Merge "Notify TaskViews of updated orientation state" into ub-launcher3-rvc-qpr-dev
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 987cd0f..b94e633 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
@@ -113,8 +113,6 @@
     private AllAppsStore mAllAppsStore;
     private AnimatorSet mIconRemoveAnimators;
     private boolean mUIUpdatePaused = false;
-    private boolean mRequiresCacheUpdate = true;
-    private boolean mIsCacheEmpty;
     private boolean mIsDestroyed = false;
 
 
@@ -350,7 +348,6 @@
             fillGapsWithPrediction();
             return;
         }
-        mIsCacheEmpty = apps.isEmpty();
         int count = Math.min(ranks.size(), apps.size());
         List<WorkspaceItemInfo> items = new ArrayList<>(count);
         for (int i = 0; i < count; i++) {
@@ -393,14 +390,7 @@
         }
         updateDependencies();
         fillGapsWithPrediction();
-        cachePredictionComponentKeysIfNecessary(componentKeys);
-    }
-
-    private void cachePredictionComponentKeysIfNecessary(ArrayList<ComponentKey> componentKeys) {
-        if (!mRequiresCacheUpdate && componentKeys.isEmpty() == mIsCacheEmpty) return;
         mPredictionModel.cachePredictionComponentKeys(componentKeys);
-        mIsCacheEmpty = componentKeys.isEmpty();
-        mRequiresCacheUpdate = false;
     }
 
     private void updateDependencies() {
@@ -429,7 +419,6 @@
             notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
                     AppTargetEvent.ACTION_PIN, workspaceItemInfo));
         }
-        mRequiresCacheUpdate = true;
     }
 
     private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
@@ -583,7 +572,6 @@
         }
         mDragObject = null;
         fillGapsWithPrediction(true, this::removeOutlineDrawings);
-        mRequiresCacheUpdate = true;
     }
 
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index de83caf..9310685 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -24,6 +24,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.util.Log;
 import android.view.animation.Interpolator;
 
@@ -52,17 +53,17 @@
 
     private final BaseActivityInterface<?, T> mActivityInterface;
     // The id of the currently running task that is transitioning to overview.
-    private final int mTargetTaskId;
+    private final RunningTaskInfo mTargetTask;
     private final RecentsAnimationDeviceState mDeviceState;
 
     private T mActivity;
     private RecentsView mRecentsView;
 
     AppToOverviewAnimationProvider(
-            BaseActivityInterface<?, T> activityInterface, int targetTaskId,
+            BaseActivityInterface<?, T> activityInterface, RunningTaskInfo targetTask,
             RecentsAnimationDeviceState deviceState) {
         mActivityInterface = activityInterface;
-        mTargetTaskId = targetTaskId;
+        mTargetTask = targetTask;
         mDeviceState = deviceState;
     }
 
@@ -73,7 +74,7 @@
      * @param wasVisible true if it was visible before
      */
     boolean onActivityReady(T activity, Boolean wasVisible) {
-        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTaskId);
+        activity.<RecentsView>getOverviewPanel().showCurrentTask(mTargetTask);
         AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
         BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
                 mDeviceState,
@@ -122,7 +123,8 @@
                 wallpaperTargets, MODE_CLOSING);
 
         // Use the top closing app to determine the insets for the animation
-        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId);
+        RemoteAnimationTargetCompat runningTaskTarget = mTargetTask == null ? null
+                : targets.findTask(mTargetTask.taskId);
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
             return pa.buildAnim();
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 ca8741d..a3f1dd4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
@@ -42,6 +42,7 @@
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.Animator;
 import android.animation.TimeInterpolator;
@@ -90,6 +91,7 @@
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TaskInfoCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
 /**
@@ -395,7 +397,8 @@
                 mOnDeferredActivityLaunch);
 
         mGestureState.runOnceAtState(STATE_END_TARGET_SET,
-                () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(),
+                () -> mDeviceState.getRotationTouchHelper().
+                        onEndTargetCalculated(mGestureState.getEndTarget(),
                         mActivityInterface));
 
         notifyGestureStartedAsync();
@@ -421,7 +424,7 @@
     }
 
     protected void notifyGestureAnimationStartToRecents() {
-        mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
+        mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
     }
 
     private void launcherFrameDrawn() {
@@ -940,7 +943,8 @@
         @Override
         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
-            if (task.taskId == mGestureState.getRunningTaskId()) {
+            if (task.taskId == mGestureState.getRunningTaskId()
+                    && TaskInfoCompat.getActivityType(task) != ACTIVITY_TYPE_HOME) {
                 // Since this is an edge case, just cancel and relaunch with default activity
                 // options (since we don't know if there's an associated app icon to launch from)
                 endRunningWindowAnim(true /* cancel */);
@@ -1327,6 +1331,6 @@
 
     private static boolean isNotInRecents(RemoteAnimationTargetCompat app) {
         return app.isNotInRecents
-                || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
+                || app.activityType == ACTIVITY_TYPE_HOME;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 33b9cde..d1da0c1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -140,7 +140,7 @@
     }
 
     @Override
-    public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+    public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
         // no-op, fake landscape not supported for 3P
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index edefbe1..7cd49fe 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -105,7 +105,7 @@
         // recents, we assume the first task is invisible, making translation off by one task.
         launcher.getStateManager().reapplyState();
         launcher.getRootView().setForceHideBackArrow(false);
-        notifyRecentsOfOrientation(deviceState);
+        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
     }
 
     @Override
@@ -120,7 +120,7 @@
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
             boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
-        notifyRecentsOfOrientation(deviceState);
+        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
             @Override
             public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
@@ -228,7 +228,7 @@
 
 
     @Override
-    public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) {
+    public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
         final StateManager<LauncherState> stateManager = getCreatedActivity().getStateManager();
         stateManager.addStateListener(
                 new StateManager.StateListener<LauncherState>() {
@@ -244,11 +244,11 @@
                 });
     }
 
-    private void notifyRecentsOfOrientation(RecentsAnimationDeviceState deviceState) {
+    private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
         // reset layout on swipe to home
         RecentsView recentsView = getCreatedActivity().getOverviewPanel();
-        recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(),
-                deviceState.getDisplayRotation());
+        recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+                rotationTouchHelper.getDisplayRotation());
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index 434a929..4879db7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -165,7 +165,7 @@
             mActivityInterface = mOverviewComponentObserver.getActivityInterface();
             mCreateTime = SystemClock.elapsedRealtime();
             mAnimationProvider = new AppToOverviewAnimationProvider<>(mActivityInterface,
-                    RecentsModel.getRunningTaskId(), mDeviceState);
+                    ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
 
             // Preload the plan
             mRecentsModel.getTasks(null);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 2962234..e54a21c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -82,7 +82,8 @@
         mTransformParams = transformParams;
 
         mTaskViewSimulator.setLayoutRotation(
-                mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation());
+                mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
+                mDeviceState.getRotationTouchHelper().getDisplayRotation());
     }
 
     protected void initTransitionEndpoints(DeviceProfile dp) {
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 e9614d1..db512fa 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
@@ -19,6 +19,7 @@
 import static android.view.Surface.ROTATION_0;
 
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
 import android.annotation.SuppressLint;
@@ -146,26 +147,29 @@
          */
         public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
                 boolean rotated) {
-            final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
+            getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
 
-            getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+            if (thumbnail != null) {
+                getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
+                final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
 
-            getActionsView().setCallbacks(new OverlayUICallbacks() {
-                @Override
-                public void onShare() {
-                    if (isAllowedByPolicy) {
-                        mImageApi.startShareActivity();
-                    } else {
-                        showBlockedByPolicyMessage();
+                getActionsView().setCallbacks(new OverlayUICallbacks() {
+                    @Override
+                    public void onShare() {
+                        if (isAllowedByPolicy) {
+                            mImageApi.startShareActivity();
+                        } else {
+                            showBlockedByPolicyMessage();
+                        }
                     }
-                }
 
-                @SuppressLint("NewApi")
-                @Override
-                public void onScreenshot() {
-                    saveScreenshot(task);
-                }
-            });
+                    @SuppressLint("NewApi")
+                    @Override
+                    public void onScreenshot() {
+                        saveScreenshot(task);
+                    }
+                });
+            }
         }
 
         /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 601f87f..13adff5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -258,6 +258,7 @@
 
     private static boolean sConnected = false;
     private static boolean sIsInitialized = false;
+    private RotationTouchHelper mRotationTouchHelper;
 
     public static boolean isConnected() {
         return sConnected;
@@ -298,6 +299,7 @@
         mDeviceState = new RecentsAnimationDeviceState(this);
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
         mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
+        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         ProtoTracer.INSTANCE.get(this).add(this);
 
         sConnected = true;
@@ -326,7 +328,7 @@
         mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
                 mMainChoreographer, this::onInputEvent);
 
-        mDeviceState.updateGestureTouchRegions();
+        mRotationTouchHelper.updateGestureTouchRegions();
     }
 
     /**
@@ -470,9 +472,9 @@
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
             }
-            mDeviceState.setOrientationTransformIfNeeded(event);
+            mRotationTouchHelper.setOrientationTransformIfNeeded(event);
 
-            if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+            if (mRotationTouchHelper.isInSwipeUpTouchRegion(event)) {
                 if (TestProtocol.sDebugTracing) {
                     Log.d(TestProtocol.NO_SWIPE_TO_HOME,
                             "TouchInteractionService.onInputEvent:isInSwipeUpTouchRegion");
@@ -509,7 +511,7 @@
             // Other events
             if (mUncheckedConsumer != InputConsumer.NO_OP) {
                 // Only transform the event if we are handling it in a proper consumer
-                mDeviceState.setOrientationTransformIfNeeded(event);
+                mRotationTouchHelper.setOrientationTransformIfNeeded(event);
             }
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
index d20bbe9..ffe9d6a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -76,7 +76,7 @@
      */
     public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
         mHomeTaskInfo = homeTaskInfo;
-        onGestureAnimationStart(homeTaskInfo == null ? -1 : homeTaskInfo.taskId);
+        onGestureAnimationStart(homeTaskInfo);
     }
 
     /**
@@ -107,14 +107,15 @@
     }
 
     @Override
-    protected boolean shouldAddDummyTaskView(int runningTaskId) {
-        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId
+    protected boolean shouldAddDummyTaskView(RunningTaskInfo runningTaskInfo) {
+        if (mHomeTaskInfo != null && runningTaskInfo != null &&
+                mHomeTaskInfo.taskId == runningTaskInfo.taskId
                 && getTaskViewCount() == 0) {
             // Do not add a dummy task if we are running over home with empty recents, so that we
             // show the empty recents message instead of showing a dummy task and later removing it.
             return false;
         }
-        return super.shouldAddDummyTaskView(runningTaskId);
+        return super.shouldAddDummyTaskView(runningTaskInfo);
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 5ad48eb..0c2c92c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -99,7 +99,8 @@
             case ACTION_POINTER_DOWN: {
                 if (mState == STATE_INACTIVE) {
                     int pointerIndex = ev.getActionIndex();
-                    if (mDeviceState.isInSwipeUpTouchRegion(ev, pointerIndex)
+                    if (mDeviceState.getRotationTouchHelper()
+                            .isInSwipeUpTouchRegion(ev, pointerIndex)
                             && mDelegate.allowInterceptByParent()) {
                         setActive(ev);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 3a97216..a676390 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -147,7 +147,7 @@
                 if (!mThresholdCrossed) {
                     // Cancel interaction in case of multi-touch interaction
                     int ptrIdx = ev.getActionIndex();
-                    if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
+                    if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) {
                         int action = ev.getAction();
                         ev.setAction(ACTION_CANCEL);
                         finishTouchTracking(ev);
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 26df9c7..6259f1f 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
@@ -59,6 +59,7 @@
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
@@ -86,6 +87,7 @@
     private final NavBarPosition mNavBarPosition;
     private final TaskAnimationManager mTaskAnimationManager;
     private final GestureState mGestureState;
+    private final RotationTouchHelper mRotationTouchHelper;
     private RecentsAnimationCallbacks mActiveCallbacks;
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final InputMonitorCompat mInputMonitorCompat;
@@ -163,6 +165,7 @@
 
         mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
+        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
     }
 
     @Override
@@ -230,7 +233,7 @@
                 if (!mPassedPilferInputSlop) {
                     // Cancel interaction in case of multi-touch interaction
                     int ptrIdx = ev.getActionIndex();
-                    if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
+                    if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) {
                         forceCancelGesture(ev);
                     }
                 }
@@ -424,7 +427,7 @@
 
     @Override
     public void notifyOrientationSetup() {
-        mDeviceState.onStartGesture();
+        mRotationTouchHelper.onStartGesture();
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index d972c0f..1c77a05 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.PointF;
 import android.view.MotionEvent;
 
@@ -80,9 +79,7 @@
 
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
-        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_HOME)
-                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        mContext.startActivity(mGestureState.getHomeIntent());
         ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int pageIndex = -1; // This number doesn't reflect workspace page index.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 3cafd42..4120331 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -205,13 +205,15 @@
         ResourceProvider rp = DynamicResource.provider(v.getContext());
         float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
         float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
+        float endTransY = 0;
+        float springVelocity = Math.abs(mVelocity) * Math.signum(endTransY - mSpringTransY);
         ValueAnimator springTransY = new SpringAnimationBuilder(v.getContext())
                 .setStiffness(stiffness)
                 .setDampingRatio(damping)
                 .setMinimumVisibleChange(1f)
                 .setStartValue(mSpringTransY)
-                .setEndValue(0)
-                .setStartVelocity(mVelocity)
+                .setEndValue(endTransY)
+                .setStartVelocity(springVelocity)
                 .build(v, VIEW_TRANSLATE_Y);
         springTransY.setStartDelay(startDelay);
         springTransY.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
index 46013d3..c9ed498 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -103,6 +103,7 @@
         mSizeStrategy = sizeStrategy;
 
         mOrientationState = new RecentsOrientedState(context, sizeStrategy, i -> { });
+        mOrientationState.setGestureActive(true);
 
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
         mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index a2da398..79d57c5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -70,12 +70,14 @@
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
-            DISABLED_ROTATED})
+            DISABLED_ROTATED,
+            DISABLED_NO_THUMBNAIL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ActionsDisabledFlags { }
 
     public static final int DISABLED_SCROLLING = 1 << 0;
     public static final int DISABLED_ROTATED = 1 << 1;
+    public static final int DISABLED_NO_THUMBNAIL = 1 << 2;
 
     private static final int INDEX_CONTENT_ALPHA = 0;
     private static final int INDEX_VISIBILITY_ALPHA = 1;
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 602ab0b..027a737 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
@@ -55,10 +55,8 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.ComponentName;
+import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Point;
@@ -135,6 +133,7 @@
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.LauncherEventUtil;
@@ -147,7 +146,7 @@
 /**
  * A list of recent tasks.
  */
-@TargetApi(Build.VERSION_CODES.P)
+@TargetApi(Build.VERSION_CODES.R)
 public abstract class RecentsView<T extends StatefulActivity> extends PagedView implements
         Insettable, TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
         InvariantDeviceProfile.OnIDPChangeListener, TaskVisualsChangeListener,
@@ -1003,7 +1002,9 @@
         mDwbToastShown = false;
         mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0);
         LayoutUtils.setViewEnabled(mActionsView, true);
-        mOrientationState.setGestureActive(false);
+        if (mOrientationState.setGestureActive(false)) {
+            updateOrientationHandler();
+        }
     }
 
     public @Nullable TaskView getRunningTaskView() {
@@ -1039,10 +1040,13 @@
     /**
      * Called when a gesture from an app is starting.
      */
-    public void onGestureAnimationStart(int runningTaskId) {
+    public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
         // This needs to be called before the other states are set since it can create the task view
-        mOrientationState.setGestureActive(true);
-        showCurrentTask(runningTaskId);
+        if (mOrientationState.setGestureActive(true)) {
+            updateOrientationHandler();
+        }
+
+        showCurrentTask(runningTaskInfo);
         setEnableFreeScroll(false);
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
@@ -1104,7 +1108,9 @@
      * Called when a gesture from an app has finished.
      */
     public void onGestureAnimationEnd() {
-        mOrientationState.setGestureActive(false);
+        if (mOrientationState.setGestureActive(false)) {
+            updateOrientationHandler();
+        }
 
         setOnScrollChangeListener(null);
         setEnableFreeScroll(true);
@@ -1120,8 +1126,8 @@
     /**
      * Returns true if we should add a dummy taskView for the running task id
      */
-    protected boolean shouldAddDummyTaskView(int runningTaskId) {
-        return getTaskView(runningTaskId) == null;
+    protected boolean shouldAddDummyTaskView(RunningTaskInfo runningTaskInfo) {
+        return runningTaskInfo != null && getTaskView(runningTaskInfo.taskId) == null;
     }
 
     /**
@@ -1130,8 +1136,8 @@
      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
      * is called.  Also scrolls the view to this task.
      */
-    public void showCurrentTask(int runningTaskId) {
-        if (shouldAddDummyTaskView(runningTaskId)) {
+    public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
+        if (shouldAddDummyTaskView(runningTaskInfo)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView = mTaskViewPool.getView();
@@ -1141,10 +1147,7 @@
             }
             // The temporary running task is only used for the duration between the start of the
             // gesture and the task list is loaded and applied
-            mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(),
-                    new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
-                    false, true, false, false, new ActivityManager.TaskDescription(), 0,
-                    new ComponentName("", ""), false);
+            mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false);
             taskView.bind(mTmpRunningTask, mOrientationState);
 
             // Measure and layout immediately so that the scroll values is updated instantly
@@ -1155,7 +1158,7 @@
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
-        setCurrentTask(runningTaskId);
+        setCurrentTask(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
         setCurrentPage(getRunningTaskIndex());
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
@@ -1645,6 +1648,9 @@
         super.setVisibility(visibility);
         if (mActionsView != null) {
             mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE);
+            if (visibility != VISIBLE) {
+                mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false);
+            }
         }
     }
 
@@ -1673,7 +1679,7 @@
                 : View.LAYOUT_DIRECTION_RTL);
         mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated());
         mActivity.getDragLayer().recreateControllers();
-        boolean isInLandscape = mOrientationState.getTouchRotation() != 0
+        boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0
                 || mOrientationState.getRecentsActivityRotation() != ROTATION_0;
         mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION,
                 !mOrientationState.canRecentsActivityRotate() && isInLandscape);
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 ef66b7a..8b49f2c 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
@@ -169,7 +169,9 @@
         }
         if (mIsOpen) {
             mOptionLayout.removeAllViews();
-            populateAndLayoutMenu();
+            if (!populateAndLayoutMenu()) {
+                close(false);
+            }
         }
     }
 
@@ -186,14 +188,22 @@
         }
         mActivity.getDragLayer().addView(this);
         mTaskView = taskView;
-        populateAndLayoutMenu();
+        if (!populateAndLayoutMenu()) {
+            return false;
+        }
         post(this::animateOpen);
         return true;
     }
 
-    private void populateAndLayoutMenu() {
+    /** @return true if successfully able to populate task view menu, false otherwise */
+    private boolean populateAndLayoutMenu() {
+        if (mTaskView.getTask().icon == null) {
+            // Icon may not be loaded
+            return false;
+        }
         addMenuOptions(mTaskView);
         orientAroundTaskView(mTaskView);
+        return true;
     }
 
     private void addMenuOptions(TaskView taskView) {
@@ -240,8 +250,10 @@
         setLayoutParams(params);
         setScaleX(taskView.getScaleX());
         setScaleY(taskView.getScaleY());
+        boolean canActivityRotate = taskView.getRecentsView()
+            .mOrientationState.canRecentsActivityRotate();
         mOptionLayout.setOrientation(orientationHandler
-                .getTaskMenuLayoutOrientation(mOptionLayout));
+                .getTaskMenuLayoutOrientation(canActivityRotate, mOptionLayout));
         setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top,
             taskView.getPagedOrientationHandler());
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index b2f937f..37f6faf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -357,7 +357,7 @@
     }
 
     private void updateOverlay() {
-        if (mOverlayEnabled && mBitmapShader != null && mThumbnailData != null) {
+        if (mOverlayEnabled) {
             mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
                     mPreviewPositionHelper.mIsOrientationChanged);
         } else {
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index 93b64e6..c148a4b 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.util.LauncherUIHelper.doLayout;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 
@@ -50,7 +51,7 @@
     }
 
     @Test
-    public void testRecets_showCurrentTask() {
+    public void testRecents_showCurrentTask() {
         ActivityController<RecentsActivity> controller =
                 Robolectric.buildActivity(RecentsActivity.class);
 
@@ -58,7 +59,10 @@
         doLayout(activity);
 
         FallbackRecentsView frv = activity.getOverviewPanel();
-        frv.showCurrentTask(22);
+
+        RunningTaskInfo dummyTask = new RunningTaskInfo();
+        dummyTask.taskId = 22;
+        frv.showCurrentTask(dummyTask);
         doLayout(activity);
 
         ThumbnailData thumbnailData = new ThumbnailData();
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 47ce320..6b941be 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -157,6 +157,12 @@
     @Override
     protected void onDeferredResumed() {
         super.onDeferredResumed();
+        handlePendingActivityRequest();
+    }
+
+    @Override
+    protected void handlePendingActivityRequest() {
+        super.handlePendingActivityRequest();
         if (mPendingActivityRequestCode != -1 && isInState(NORMAL)) {
             // Remove any active ProxyActivityStarter task and send RESULT_CANCELED to Launcher.
             onActivityResult(mPendingActivityRequestCode, RESULT_CANCELED, null);
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index fe7b946..bd97f65 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3;
 
-import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -62,7 +60,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Pair;
-import android.util.TypedValue;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -875,10 +872,8 @@
                             }
                         });
                     } else {
-                        float velocityDpPerS = DynamicResource.provider(mLauncher)
+                        float velocityPxPerS = DynamicResource.provider(mLauncher)
                                 .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
-                        float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP,
-                                velocityDpPerS, mLauncher.getResources().getDisplayMetrics());
                         anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false)
                                 .getAnimators());
                     }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 2b698bd..5600589 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -150,7 +150,7 @@
         return deviceState.isInDeferredGestureRegion(ev);
     }
 
-    public abstract void onExitOverview(RecentsAnimationDeviceState deviceState,
+    public abstract void onExitOverview(RotationTouchHelper deviceState,
             Runnable exitRunnable);
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 0a70bd6..dd80bd7 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -16,11 +16,9 @@
 package com.android.quickstep;
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
-import static android.view.Surface.ROTATION_0;
 
 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
 import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -49,23 +47,19 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.view.MotionEvent;
-import android.view.OrientationEventListener;
 
 import androidx.annotation.BinderThread;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
-import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
-import com.android.systemui.shared.system.TaskStackChangeListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -83,7 +77,7 @@
     private final SysUINavigationMode mSysUiNavMode;
     private final DefaultDisplay mDefaultDisplay;
     private final int mDisplayId;
-    private int mDisplayRotation;
+    private final RotationTouchHelper mRotationTouchHelper;
 
     private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
 
@@ -107,76 +101,10 @@
         }
     };
 
-    private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
-        @Override
-        public void onRecentTaskListFrozenChanged(boolean frozen) {
-            mTaskListFrozen = frozen;
-            if (frozen || mInOverview) {
-                return;
-            }
-            enableMultipleRegions(false);
-        }
-
-        @Override
-        public void onActivityRotation(int displayId) {
-            super.onActivityRotation(displayId);
-            // This always gets called before onDisplayInfoChanged() so we know how to process
-            // the rotation in that method. This is done to avoid having a race condition between
-            // the sensor readings and onDisplayInfoChanged() call
-            if (displayId != mDisplayId) {
-                return;
-            }
-
-            mPrioritizeDeviceRotation = true;
-            if (mInOverview) {
-                // reset, launcher must be rotating
-                mExitOverviewRunnable.run();
-            }
-        }
-    };
-
-    private Runnable mExitOverviewRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mInOverview = false;
-            enableMultipleRegions(false);
-        }
-    };
-
-    private OrientationTouchTransformer mOrientationTouchTransformer;
-    /**
-     * Used to listen for when the device rotates into the orientation of the current
-     * foreground app. For example, if a user quickswitches from a portrait to a fixed landscape
-     * app and then rotates rotates the device to match that orientation, this triggers calls to
-     * sysui to adjust the navbar.
-     */
-    private OrientationEventListener mOrientationListener;
-    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.
-     */
-    private int mCurrentAppRotation = -1;
-    /**
-     * This flag is set to true when the device physically changes orientations. When true,
-     * we will always report the current rotation of the foreground app whenever the display
-     * changes, as it would indicate the user's intention to rotate the foreground app.
-     */
-    private boolean mPrioritizeDeviceRotation = false;
-
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
     private final List<ComponentName> mGestureBlockedActivities;
-    private Runnable mOnDestroyFrozenTaskRunnable;
-    /**
-     * Set to true when user swipes to recents. In recents, we ignore the state of the recents
-     * task list being frozen or not to allow the user to keep interacting with nav bar rotation
-     * they went into recents with as opposed to defaulting to the default display rotation.
-     * TODO: (b/156984037) For when user rotates after entering overview
-     */
-    private boolean mInOverview;
-    private boolean mTaskListFrozen;
 
     private boolean mIsUserSetupComplete;
 
@@ -186,6 +114,8 @@
         mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
         mDisplayId = mDefaultDisplay.getInfo().id;
         runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
+        mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
+        runOnDestroy(mRotationTouchHelper::destroy);
 
         // Register for user unlocked if necessary
         mIsUserUnlocked = context.getSystemService(UserManager.class)
@@ -207,10 +137,6 @@
         };
         runOnDestroy(mExclusionListener::unregister);
 
-        Resources resources = mContext.getResources();
-        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
-                () -> QuickStepContract.getWindowCornerRadius(resources));
-
         // Register for navigation mode changes
         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
@@ -241,38 +167,6 @@
             userSetupObserver.register();
             runOnDestroy(userSetupObserver::unregister);
         }
-
-        mOrientationListener = new OrientationEventListener(context) {
-            @Override
-            public void onOrientationChanged(int degrees) {
-                int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
-                        mSensorRotation);
-                if (newRotation == mSensorRotation) {
-                    return;
-                }
-
-                mSensorRotation = newRotation;
-                mPrioritizeDeviceRotation = true;
-
-                if (newRotation == mCurrentAppRotation) {
-                    // When user rotates device to the orientation of the foreground app after
-                    // quickstepping
-                    toggleSecondaryNavBarsForRotation();
-                }
-            }
-        };
-    }
-
-    private void setupOrientationSwipeHandler() {
-        ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
-        mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
-                .unregisterTaskStackListener(mFrozenTaskListener);
-        runOnDestroy(mOnDestroyFrozenTaskRunnable);
-    }
-
-    private void destroyOrientationSwipeHandlerCallback() {
-        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
-        mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
     }
 
     private void runOnDestroy(Runnable action) {
@@ -311,13 +205,6 @@
 
         mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
 
-        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
-        if (!mMode.hasGestures && newMode.hasGestures) {
-            setupOrientationSwipeHandler();
-        } else if (mMode.hasGestures && !newMode.hasGestures){
-            destroyOrientationSwipeHandlerCallback();
-        }
-
         mMode = newMode;
     }
 
@@ -328,28 +215,10 @@
             return;
         }
 
-        mDisplayRotation = info.rotation;
-
         if (!mMode.hasGestures) {
             return;
         }
         mNavBarPosition = new NavBarPosition(mMode, info);
-        updateGestureTouchRegions();
-        mOrientationTouchTransformer.createOrAddTouchRegion(info);
-        mCurrentAppRotation = mDisplayRotation;
-
-        /* Update nav bars on the following:
-         * 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 == mSensorRotation) // switch to an app of orientation user is in
-                && !mInOverview
-                && mTaskListFrozen) {
-            toggleSecondaryNavBarsForRotation();
-        }
     }
 
     /**
@@ -464,7 +333,7 @@
      */
     public boolean canStartSystemGesture() {
         boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
-                || mTaskListFrozen;
+                || mRotationTouchHelper.isTaskListFrozen();
         return canStartWithNavHidden
                 && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
                 && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
@@ -537,33 +406,6 @@
     }
 
     /**
-     * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
-     */
-    public void updateGestureTouchRegions() {
-        if (!mMode.hasGestures) {
-            return;
-        }
-
-        mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
-    }
-
-    /**
-     * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
-     */
-    public boolean isInSwipeUpTouchRegion(MotionEvent event) {
-        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
-    }
-
-    /**
-     * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
-     *         is in the swipe up gesture region.
-     */
-    public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
-        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
-                event.getY(pointerIndex));
-    }
-
-    /**
      * Sets the region in screen space where the gestures should be deferred (ie. due to specific
      * nav bar ui).
      */
@@ -620,101 +462,13 @@
     public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
         return mAssistantAvailable
                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
-                && mOrientationTouchTransformer.touchInAssistantRegion(ev)
+                && mRotationTouchHelper.touchInAssistantRegion(ev)
                 && !isLockToAppActive()
                 && !isGestureBlockedActivity(task);
     }
 
-    /**
-     * *May* apply a transform on the motion event if it lies in the nav bar region for another
-     * orientation that is currently being tracked as a part of quickstep
-     */
-    void setOrientationTransformIfNeeded(MotionEvent event) {
-        // negative coordinates bug b/143901881
-        if (event.getX() < 0 || event.getY() < 0) {
-            event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
-        }
-        mOrientationTouchTransformer.transform(event);
-    }
-
-    private void enableMultipleRegions(boolean enable) {
-        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();
-        }
-    }
-
-    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
-            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
-        }
-    }
-
-    void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
-            BaseActivityInterface activityInterface) {
-        if (endTarget == GestureState.GestureEndTarget.RECENTS) {
-            mInOverview = true;
-            if (!mTaskListFrozen) {
-                // If we're in landscape w/o ever quickswitching, show the navbar in landscape
-                enableMultipleRegions(true);
-            }
-            activityInterface.onExitOverview(this, mExitOverviewRunnable);
-        } else if (endTarget == GestureState.GestureEndTarget.HOME) {
-            enableMultipleRegions(false);
-        } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
-            if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
-                // First gesture to start quickswitch
-                enableMultipleRegions(true);
-            } else {
-                notifySysuiOfCurrentRotation(
-                        mOrientationTouchTransformer.getCurrentActiveRotation());
-            }
-
-            // A new gesture is starting, reset the current device rotation
-            // This is done under the assumption that the user won't rotate the phone and then
-            // quickswitch in the old orientation.
-            mPrioritizeDeviceRotation = false;
-        } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
-            if (!mTaskListFrozen) {
-                // touched nav bar but didn't go anywhere and not quickswitching, do nothing
-                return;
-            }
-            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
-        }
-    }
-
-    private void notifySysuiOfCurrentRotation(int rotation) {
-        UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
-                .onQuickSwitchToNewTask(rotation));
-    }
-
-    /**
-     * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
-     * notifies system UI of the primary rotation the user is interacting with
-     */
-    private void toggleSecondaryNavBarsForRotation() {
-        mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
-        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
-    }
-
-    public int getCurrentActiveRotation() {
-        if (!mMode.hasGestures) {
-            // touch rotation should always match that of display for 3 button
-            return mDisplayRotation;
-        }
-        return mOrientationTouchTransformer.getCurrentActiveRotation();
-    }
-
-    public int getDisplayRotation() {
-        return mDisplayRotation;
+    public RotationTouchHelper getRotationTouchHelper() {
+        return mRotationTouchHelper;
     }
 
     public void dump(PrintWriter pw) {
@@ -726,9 +480,7 @@
         pw.println("  assistantAvailable=" + mAssistantAvailable);
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
-        pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
-        pw.println("  displayRotation=" + getDisplayRotation());
         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
-        mOrientationTouchTransformer.dump(pw);
+        mRotationTouchHelper.dump(pw);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 517501a..6f54ba2 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -93,15 +93,6 @@
     }
 
     /**
-     * @return The task id of the running task, or -1 if there is no current running task.
-     */
-    public static int getRunningTaskId() {
-        ActivityManager.RunningTaskInfo runningTask =
-                ActivityManagerWrapper.getInstance().getRunningTask();
-        return runningTask != null ? runningTask.id : -1;
-    }
-
-    /**
      * @return Whether the provided {@param changeId} is the latest recent tasks list id.
      */
     public boolean isTaskListValid(int changeId) {
@@ -140,7 +131,9 @@
         }
 
         // Keep the cache up to date with the latest thumbnails
-        int runningTaskId = RecentsModel.getRunningTaskId();
+        ActivityManager.RunningTaskInfo runningTask =
+                ActivityManagerWrapper.getInstance().getRunningTask();
+        int runningTaskId = runningTask != null ? runningTask.id : -1;
         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {
             for (Task task : tasks) {
                 if (task.key.id == runningTaskId) {
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
new file mode 100644
index 0000000..d89ca10
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -0,0 +1,365 @@
+/*
+ * 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.quickstep;
+
+import static android.view.Surface.ROTATION_0;
+
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class RotationTouchHelper implements
+        SysUINavigationMode.NavigationModeChangeListener,
+        DefaultDisplay.DisplayInfoChangeListener {
+    public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
+            new MainThreadInitializedObject<>(RotationTouchHelper::new);
+
+    private final OrientationTouchTransformer mOrientationTouchTransformer;
+    private final DefaultDisplay mDefaultDisplay;
+    private final SysUINavigationMode mSysUiNavMode;
+    private final int mDisplayId;
+    private int mDisplayRotation;
+
+    private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
+
+    private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
+
+    private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
+        @Override
+        public void onRecentTaskListFrozenChanged(boolean frozen) {
+            mTaskListFrozen = frozen;
+            if (frozen || mInOverview) {
+                return;
+            }
+            enableMultipleRegions(false);
+        }
+
+        @Override
+        public void onActivityRotation(int displayId) {
+            super.onActivityRotation(displayId);
+            // This always gets called before onDisplayInfoChanged() so we know how to process
+            // the rotation in that method. This is done to avoid having a race condition between
+            // the sensor readings and onDisplayInfoChanged() call
+            if (displayId != mDisplayId) {
+                return;
+            }
+
+            mPrioritizeDeviceRotation = true;
+            if (mInOverview) {
+                // reset, launcher must be rotating
+                mExitOverviewRunnable.run();
+            }
+        }
+    };
+
+    private Runnable mExitOverviewRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mInOverview = false;
+            enableMultipleRegions(false);
+        }
+    };
+
+    /**
+     * Used to listen for when the device rotates into the orientation of the current foreground
+     * app. For example, if a user quickswitches from a portrait to a fixed landscape app and then
+     * rotates rotates the device to match that orientation, this triggers calls to sysui to adjust
+     * the navbar.
+     */
+    private OrientationEventListener mOrientationListener;
+    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.
+     */
+    private int mCurrentAppRotation = -1;
+    /**
+     * This flag is set to true when the device physically changes orientations. When true, we will
+     * always report the current rotation of the foreground app whenever the display changes, as it
+     * would indicate the user's intention to rotate the foreground app.
+     */
+    private boolean mPrioritizeDeviceRotation = false;
+    private Runnable mOnDestroyFrozenTaskRunnable;
+    /**
+     * Set to true when user swipes to recents. In recents, we ignore the state of the recents
+     * task list being frozen or not to allow the user to keep interacting with nav bar rotation
+     * they went into recents with as opposed to defaulting to the default display rotation.
+     * TODO: (b/156984037) For when user rotates after entering overview
+     */
+    private boolean mInOverview;
+    private boolean mTaskListFrozen;
+
+
+    private final Context mContext;
+
+    private RotationTouchHelper(Context context) {
+        mContext = context;
+        Resources resources = mContext.getResources();
+        mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
+        mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
+        mDisplayId = mDefaultDisplay.getInfo().id;
+
+        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+                () -> QuickStepContract.getWindowCornerRadius(resources));
+
+        // Register for navigation mode changes
+        onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
+        runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
+
+        mOrientationListener = new OrientationEventListener(context) {
+            @Override
+            public void onOrientationChanged(int degrees) {
+                int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
+                        mSensorRotation);
+                if (newRotation == mSensorRotation) {
+                    return;
+                }
+
+                mSensorRotation = newRotation;
+                mPrioritizeDeviceRotation = true;
+
+                if (newRotation == mCurrentAppRotation) {
+                    // When user rotates device to the orientation of the foreground app after
+                    // quickstepping
+                    toggleSecondaryNavBarsForRotation();
+                }
+            }
+        };
+    }
+
+    private void setupOrientationSwipeHandler() {
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
+        mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
+                .unregisterTaskStackListener(mFrozenTaskListener);
+        runOnDestroy(mOnDestroyFrozenTaskRunnable);
+    }
+
+    private void destroyOrientationSwipeHandlerCallback() {
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
+        mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
+    }
+
+    private void runOnDestroy(Runnable action) {
+        mOnDestroyActions.add(action);
+    }
+
+    /**
+     * Cleans up all the registered listeners and receivers.
+     */
+    public void destroy() {
+        for (Runnable r : mOnDestroyActions) {
+            r.run();
+        }
+    }
+
+    public boolean isTaskListFrozen() {
+        return mTaskListFrozen;
+    }
+
+    public boolean touchInAssistantRegion(MotionEvent ev) {
+        return mOrientationTouchTransformer.touchInAssistantRegion(ev);
+    }
+
+    /**
+     * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
+     */
+    public void updateGestureTouchRegions() {
+        if (!mMode.hasGestures) {
+            return;
+        }
+
+        mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
+    }
+
+    /**
+     * @return whether the coordinates of the {@param event} is in the swipe up gesture region.
+     */
+    public boolean isInSwipeUpTouchRegion(MotionEvent event) {
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
+    }
+
+    /**
+     * @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
+     *         is in the swipe up gesture region.
+     */
+    public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
+                event.getY(pointerIndex));
+    }
+
+
+    @Override
+    public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
+        mDefaultDisplay.removeChangeListener(this);
+        mDefaultDisplay.addChangeListener(this);
+        onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
+
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
+        if (!mMode.hasGestures && newMode.hasGestures) {
+            setupOrientationSwipeHandler();
+        } else if (mMode.hasGestures && !newMode.hasGestures){
+            destroyOrientationSwipeHandlerCallback();
+        }
+
+        mMode = newMode;
+    }
+
+    public int getDisplayRotation() {
+        return mDisplayRotation;
+    }
+
+    @Override
+    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+        if (info.id != mDisplayId|| flags == CHANGE_FRAME_DELAY) {
+            // ignore displays that aren't running launcher and frame refresh rate changes
+            return;
+        }
+
+        mDisplayRotation = info.rotation;
+
+        if (!mMode.hasGestures) {
+            return;
+        }
+        updateGestureTouchRegions();
+        mOrientationTouchTransformer.createOrAddTouchRegion(info);
+        mCurrentAppRotation = mDisplayRotation;
+
+        /* Update nav bars on the following:
+         * 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 == mSensorRotation) // switch to an app of orientation user is in
+                && !mInOverview
+                && mTaskListFrozen) {
+            toggleSecondaryNavBarsForRotation();
+        }
+    }
+
+    /**
+     * *May* apply a transform on the motion event if it lies in the nav bar region for another
+     * orientation that is currently being tracked as a part of quickstep
+     */
+    void setOrientationTransformIfNeeded(MotionEvent event) {
+        // negative coordinates bug b/143901881
+        if (event.getX() < 0 || event.getY() < 0) {
+            event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
+        }
+        mOrientationTouchTransformer.transform(event);
+    }
+
+    private void enableMultipleRegions(boolean enable) {
+        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();
+        }
+    }
+
+    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
+            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+        }
+    }
+
+    void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
+            BaseActivityInterface activityInterface) {
+        if (endTarget == GestureState.GestureEndTarget.RECENTS) {
+            mInOverview = true;
+            if (!mTaskListFrozen) {
+                // If we're in landscape w/o ever quickswitching, show the navbar in landscape
+                enableMultipleRegions(true);
+            }
+            activityInterface.onExitOverview(this, mExitOverviewRunnable);
+        } else if (endTarget == GestureState.GestureEndTarget.HOME) {
+            enableMultipleRegions(false);
+        } else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
+            if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
+                // First gesture to start quickswitch
+                enableMultipleRegions(true);
+            } else {
+                notifySysuiOfCurrentRotation(
+                        mOrientationTouchTransformer.getCurrentActiveRotation());
+            }
+
+            // A new gesture is starting, reset the current device rotation
+            // This is done under the assumption that the user won't rotate the phone and then
+            // quickswitch in the old orientation.
+            mPrioritizeDeviceRotation = false;
+        } else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
+            if (!mTaskListFrozen) {
+                // touched nav bar but didn't go anywhere and not quickswitching, do nothing
+                return;
+            }
+            notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+        }
+    }
+
+    private void notifySysuiOfCurrentRotation(int rotation) {
+        UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
+                .onQuickSwitchToNewTask(rotation));
+    }
+
+    /**
+     * Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
+     * notifies system UI of the primary rotation the user is interacting with
+     */
+    private void toggleSecondaryNavBarsForRotation() {
+        mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
+        notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
+    }
+
+    public int getCurrentActiveRotation() {
+        if (!mMode.hasGestures) {
+            // touch rotation should always match that of display for 3 button
+            return mDisplayRotation;
+        }
+        return mOrientationTouchTransformer.getCurrentActiveRotation();
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("RotationTouchHelper:");
+        pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
+        pw.println("  displayRotation=" + getDisplayRotation());
+        mOrientationTouchTransformer.dump(pw);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index ae19d73..f7bd1e2 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -49,7 +49,7 @@
             Rect taskSize = new Rect();
             LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
                     orientationHandler);
-            return (dp.heightPx - taskSize.height()) / 2;
+            return orientationHandler.getDistanceToBottomOfRect(dp, taskSize);
         }
         int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
         int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index b359f0f..d822b6c 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -188,8 +188,9 @@
     /**
      * Sets if the swipe up gesture is currently running or not
      */
-    public void setGestureActive(boolean isGestureActive) {
+    public boolean setGestureActive(boolean isGestureActive) {
         setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
+        return update(mTouchRotation, mDisplayRotation);
     }
 
     /**
@@ -202,27 +203,19 @@
      */
     public boolean update(
             @SurfaceRotation int touchRotation, @SurfaceRotation int displayRotation) {
-        int recentsActivityRotation = inferRecentsActivityRotation(displayRotation);
-        if (mDisplayRotation == displayRotation
-                && mTouchRotation == touchRotation
-                && mRecentsActivityRotation == recentsActivityRotation) {
-            return false;
-        }
-
-        mRecentsActivityRotation = recentsActivityRotation;
+        mRecentsActivityRotation = inferRecentsActivityRotation(displayRotation);
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
         mPreviousRotation = touchRotation;
 
-        if (mRecentsActivityRotation == mTouchRotation || canRecentsActivityRotate()) {
+        PagedOrientationHandler oldHandler = mOrientationHandler;
+        if (mRecentsActivityRotation == mTouchRotation
+                || (canRecentsActivityRotate() && (mFlags & FLAG_SWIPE_UP_NOT_RUNNING) != 0)) {
             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
             if (DEBUG) {
                 Log.d(TAG, "current RecentsOrientedState: " + this);
             }
-            return true;
-        }
-
-        if (mTouchRotation == ROTATION_90) {
+        } else if (mTouchRotation == ROTATION_90) {
             mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
         } else if (mTouchRotation == ROTATION_270) {
             mOrientationHandler = PagedOrientationHandler.SEASCAPE;
@@ -232,7 +225,7 @@
         if (DEBUG) {
             Log.d(TAG, "current RecentsOrientedState: " + this);
         }
-        return true;
+        return oldHandler != mOrientationHandler;
     }
 
     @SurfaceRotation
diff --git a/res/values/config.xml b/res/values/config.xml
index ca25325..75fcc90 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -142,7 +142,7 @@
 
     <item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="staggered_stiffness" type="dimen" format="float">150</item>
-    <dimen name="unlock_staggered_velocity_dp_per_s">3dp</dimen>
+    <dimen name="unlock_staggered_velocity_dp_per_s">4dp</dimen>
 
     <item name="hint_scale_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="hint_scale_stiffness" type="dimen" format="float">200</item>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 60b6da6..48819cb 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -331,10 +331,7 @@
     public boolean onTouchEvent(MotionEvent event) {
         // ignore events if they happen in padding area
         if (event.getAction() == MotionEvent.ACTION_DOWN
-                && (event.getY() < getPaddingTop()
-                || event.getX() < getPaddingLeft()
-                || event.getY() > getHeight() - getPaddingBottom()
-                || event.getX() > getWidth() - getPaddingRight())) {
+                && shouldIgnoreTouchDown(event.getX(), event.getY())) {
             return false;
         }
         if (isLongClickable()) {
@@ -347,6 +344,16 @@
         }
     }
 
+    /**
+     * Returns true if the touch down at the provided position be ignored
+     */
+    protected boolean shouldIgnoreTouchDown(float x, float y) {
+        return y < getPaddingTop()
+                || x < getPaddingLeft()
+                || y > getHeight() - getPaddingBottom()
+                || x > getWidth() - getPaddingRight();
+    }
+
     void setStayPressed(boolean stayPressed) {
         mStayPressed = stayPressed;
         refreshDrawableState();
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index e3cd0bd..49caa93 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -369,8 +369,10 @@
         if (allAppsHasDifferentNumColumns()) {
             allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, mInfo.metrics);
             allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, mInfo.metrics);
-            allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y;
             allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
+            // We use 4 below to ensure labels are closer to their corresponding icon.
+            allAppsCellHeightPx = Math.round(allAppsIconSizePx + allAppsIconTextSizePx
+                    + (4 * allAppsIconDrawablePaddingPx));
         } else {
             allAppsIconSizePx = iconSizePx;
             allAppsIconTextSizePx = iconTextSizePx;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0970dae..d06ae7a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -923,6 +923,7 @@
         DiscoveryBounce.showForHomeIfNeeded(this);
     }
 
+    protected void handlePendingActivityRequest() { }
 
     private void logStopAndResume(int command) {
         int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
@@ -1423,7 +1424,8 @@
                 if (!isInState(NORMAL)) {
                     // Only change state, if not already the same. This prevents cancelling any
                     // animations running as part of resume
-                    mStateManager.goToState(NORMAL);
+                    mStateManager.goToState(NORMAL, mStateManager.shouldAnimateStateChange(),
+                            this::handlePendingActivityRequest);
                 }
 
                 // Reset the apps view
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 1c18402..de2b5da 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -741,7 +741,8 @@
 
     @Override
     protected View getAccessibilityInitialFocusView() {
-        return mContent.getFirstItem();
+        View firstItem = mContent.getFirstItem();
+        return firstItem != null ? firstItem : super.getAccessibilityInitialFocusView();
     }
 
     private void closeComplete(boolean wasAnimated) {
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 5dd05db..a424f84 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -28,6 +28,7 @@
 import android.annotation.TargetApi;
 import android.app.Fragment;
 import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
@@ -56,6 +57,7 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -83,6 +85,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.uioverrides.PredictedAppIconInflater;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.views.ActivityContext;
@@ -339,14 +342,33 @@
             addInScreenFromBind(folderIcon, info);
         }
 
+        private void inflateAndAddWidgets(LauncherAppWidgetInfo info,
+                Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+            if (widgetProviderInfoMap == null) {
+                return;
+            }
+            AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get(
+                    new ComponentKey(info.providerName, info.user));
+            if (providerInfo == null) {
+                return;
+            }
+            inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo(
+                    getApplicationContext(), providerInfo));
+        }
+
         private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
             WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
                     info.providerName);
             if (widgetItem == null) {
                 return;
             }
+            inflateAndAddWidgets(info, widgetItem.widgetInfo);
+        }
+
+        private void inflateAndAddWidgets(LauncherAppWidgetInfo info,
+                LauncherAppWidgetProviderInfo providerInfo) {
             AppWidgetHostView view = new AppWidgetHostView(mContext);
-            view.setAppWidget(-1, widgetItem.widgetInfo);
+            view.setAppWidget(-1, providerInfo);
             view.updateAppWidget(null);
             view.setTag(info);
             addInScreenFromBind(view, info);
@@ -433,8 +455,13 @@
                     switch (itemInfo.itemType) {
                         case Favorites.ITEM_TYPE_APPWIDGET:
                         case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                            inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
-                                    workspaceResult.mWidgetsModel);
+                            if (mMigrated) {
+                                inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+                                        workspaceResult.mWidgetProvidersMap);
+                            } else {
+                                inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+                                        workspaceResult.mWidgetsModel);
+                            }
                             break;
                         default:
                             break;
@@ -542,7 +569,7 @@
             }
 
             return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
-                    mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
+                    mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel, null);
         }
     }
 
@@ -569,9 +596,8 @@
         public WorkspaceResult call() throws Exception {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
             loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
-            mBgDataModel.widgetsModel.update(mApp, null);
             return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
-                    mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel);
+                    mBgDataModel.cachedPredictedItems, null, mWidgetProvidersMap);
         }
     }
 
@@ -593,14 +619,17 @@
         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
         private final ArrayList<AppInfo> mCachedPredictedItems;
         private final WidgetsModel mWidgetsModel;
+        private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
 
         private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
                 ArrayList<LauncherAppWidgetInfo> appWidgets,
-                ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel) {
+                ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel,
+                Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
             mWorkspaceItems = workspaceItems;
             mAppWidgets = appWidgets;
             mCachedPredictedItems = cachedPredictedItems;
             mWidgetsModel = widgetsModel;
+            mWidgetProvidersMap = widgetProviderInfoMap;
         }
     }
 }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ebdfa8c..79467d3 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -406,7 +406,7 @@
          * to speed up the search.
          */
         private boolean findPlacement(DbEntry entry) {
-            for (int y = mNextStartY; y > 0; y--) {
+            for (int y = mNextStartY; y >= (mScreenId == 0 ? 1 /* smartspace */ : 0); y--) {
                 for (int x = mNextStartX; x < mTrgX; x++) {
                     boolean fits = mOccupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
                     boolean minFits = mOccupied.isRegionVacant(x, y, entry.minSpanX,
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 102ec31..4a64522 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -126,6 +126,8 @@
 
     private final UserManagerState mUserManagerState = new UserManagerState();
 
+    protected Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
+
     private boolean mStopped;
 
     public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
@@ -342,8 +344,6 @@
                     contentResolver.query(contentUri, null, null, null, null), contentUri, mApp,
                     mUserManagerState);
 
-            Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;
-
             try {
                 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
                         LauncherSettings.Favorites.APPWIDGET_ID);
@@ -650,10 +650,11 @@
                             final boolean wasProviderReady = !c.hasRestoreFlag(
                                     LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY);
 
-                            if (widgetProvidersMap == null) {
-                                widgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(context);
+                            if (mWidgetProvidersMap == null) {
+                                mWidgetProvidersMap = WidgetManagerHelper.getAllProvidersMap(
+                                        context);
                             }
-                            final AppWidgetProviderInfo provider = widgetProvidersMap.get(
+                            final AppWidgetProviderInfo provider = mWidgetProvidersMap.get(
                                     new ComponentKey(component, c.user));
 
                             final boolean isProviderReady = isValidProvider(provider);
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index 2daa2fe..eb68592 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -23,7 +23,6 @@
 import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.widget.Toast;
 
 import com.android.launcher3.BubbleTextView;
@@ -106,12 +105,12 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            // Show toast if user touches the drag handle (long clicks still start the drag).
-            mShowInstructionToast = mDragHandleBounds.contains((int) ev.getX(), (int) ev.getY());
-        }
-        return super.onTouchEvent(ev);
+    protected boolean shouldIgnoreTouchDown(float x, float y) {
+        // Show toast if user touches the drag handle (long clicks still start the drag).
+        mShowInstructionToast = mDragHandleBounds.contains((int) x, (int) y);
+
+        // assume the whole view as clickable
+        return false;
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 48c7734..ac1ade2 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.touch;
 
 import static android.widget.ListPopupWindow.WRAP_CONTENT;
+
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
@@ -33,6 +34,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -237,7 +239,8 @@
     }
 
     @Override
-    public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
+    public int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate,
+        LinearLayout taskMenuLayout) {
         return LinearLayout.HORIZONTAL;
     }
 
@@ -260,4 +263,10 @@
         }
         return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
     }
+
+    @SuppressWarnings("SuspiciousNameCombination")
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return rect.left;
+    }
 }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 65b1a7a..d4f5cba 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -94,8 +94,9 @@
     float getTaskMenuX(float x, View thumbnailView);
     float getTaskMenuY(float y, View thumbnailView);
     int getTaskMenuWidth(View view);
-    int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout);
+    int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate, LinearLayout taskMenuLayout);
     void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp);
+    int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
 
     /**
      * Maps the velocity from the coordinate plane of the foreground app to that
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 79e5c87..3341996 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -32,6 +32,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.LinearLayout;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.OverScroller;
@@ -236,8 +237,9 @@
     }
 
     @Override
-    public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) {
-        return taskMenuLayout.getOrientation();
+    public int getTaskMenuLayoutOrientation(boolean canRecentsActivityRotate,
+        LinearLayout taskMenuLayout) {
+        return canRecentsActivityRotate ? taskMenuLayout.getOrientation() : LinearLayout.VERTICAL;
     }
 
     @Override
@@ -257,4 +259,9 @@
         }
         return new ChildBounds(childWidth, childHeight, childRight, childTop);
     }
+
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return dp.heightPx - rect.bottom;
+    }
 }
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index d5ae2dc..e91f16d 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -18,9 +18,11 @@
 
 import android.content.res.Resources;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.view.Surface;
 import android.view.View;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 
 public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
@@ -77,4 +79,9 @@
         view.setTranslationX(0);
         view.setTranslationY(translation);
     }
+
+    @Override
+    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
+        return dp.widthPx - rect.right;
+    }
 }