Merge "Enable Grid Preview flag" into ub-launcher3-master
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 51ee216..7728207 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -39,6 +38,7 @@
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.util.FloatProperty;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -50,6 +50,7 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -88,7 +89,8 @@
 
         TaskView taskView = findTaskViewToLaunch(mLauncher, v, appTargets);
 
-        AppWindowAnimationHelper helper = new AppWindowAnimationHelper(mLauncher);
+        AppWindowAnimationHelper helper =
+            new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher);
         anim.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets,
                 wallpaperTargets, helper).setDuration(RECENTS_LAUNCH_DURATION));
 
@@ -197,7 +199,11 @@
                 return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
                         RecentsView.CONTENT_ALPHA, values);
             case INDEX_RECENTS_TRANSLATE_X_ANIM:
-                return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), VIEW_TRANSLATE_X)
+                PagedOrientationHandler orientationHandler =
+                    ((RecentsView)mLauncher.getOverviewPanel()).getPagedViewOrientedState()
+                        .getOrientationHandler();
+                FloatProperty<View> translate = orientationHandler.getPrimaryViewTranslate();
+                return new SpringAnimationBuilder<>(mLauncher.getOverviewPanel(), translate)
                         .setDampingRatio(0.8f)
                         .setStiffness(250)
                         .setValues(values)
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index 519939e..9cbe11a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -80,10 +80,10 @@
     }
 
     @Override
-    public void onDragStart(boolean start) {
+    public void onDragStart(boolean start, float startDisplacement) {
         mMotionPauseDetector.clear();
 
-        super.onDragStart(start);
+        super.onDragStart(start, startDisplacement);
 
         if (handlingOverviewAnim()) {
             mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index ad4a343..19a2bae 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -126,7 +126,7 @@
     }
 
     @Override
-    public void onDragStart(boolean start) {
+    public void onDragStart(boolean start, float startDisplacement) {
         initCurrentAnimation();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 34fc3e4..ab634a4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -94,8 +94,8 @@
     }
 
     @Override
-    public void onDragStart(boolean start) {
-        super.onDragStart(start);
+    public void onDragStart(boolean start, float startDisplacement) {
+        super.onDragStart(start, startDisplacement);
 
         mReachedOverview = false;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 799f1ad..715529e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -116,12 +116,13 @@
         mLauncher = launcher;
         mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
         mShelfPeekAnim = mLauncher.getShelfPeekAnim();
+        mRecentsView = mLauncher.getOverviewPanel();
         mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
-        mYRange = LayoutUtils.getShelfTrackingDistance(mLauncher, mLauncher.getDeviceProfile());
+        mYRange = LayoutUtils.getShelfTrackingDistance(
+            mLauncher, mLauncher.getDeviceProfile());
         mMotionPauseDetector = new MotionPauseDetector(mLauncher);
         mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
-        mRecentsView = mLauncher.getOverviewPanel();
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 912be98..d5b221d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -92,8 +92,8 @@
     }
 
     @Override
-    public void onDragStart(boolean start) {
-        super.onDragStart(start);
+    public void onDragStart(boolean start, float startDisplacement) {
+        super.onDragStart(start, startDisplacement);
         mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
         mTaskToLaunch = mLauncher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
         ActivityManagerWrapper.getInstance()
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index f79ad25..e0532ac 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.touch.BaseSwipeDetector;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.FlingBlockCheck;
@@ -77,7 +78,9 @@
     public TaskViewTouchController(T activity) {
         mActivity = activity;
         mRecentsView = activity.getOverviewPanel();
-        mDetector = new SingleAxisSwipeDetector(activity, this, SingleAxisSwipeDetector.VERTICAL);
+        SingleAxisSwipeDetector.Direction dir =
+            mRecentsView.getPagedOrientationHandler().getOppositeSwipeDirection();
+        mDetector = new SingleAxisSwipeDetector(activity, this, dir);
     }
 
     private boolean canInterceptTouch() {
@@ -190,15 +193,18 @@
             mPendingAnimation = null;
         }
 
+        PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
         mCurrentAnimationIsGoingUp = goingUp;
         BaseDragLayer dl = mActivity.getDragLayer();
-        long maxDuration = (long) (2 * dl.getHeight());
-
+        final int secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl);
+        long maxDuration = (long) (2 * secondaryLayerDimension);
+        int verticalFactor = -orientationHandler.getTaskDismissDirectionFactor();
+        int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
         if (goingUp) {
             mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration);
 
-            mEndDisplacement = -mTaskBeingDragged.getHeight();
+            mEndDisplacement = -secondaryTaskDimension;
         } else {
             mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
                     mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
@@ -207,6 +213,7 @@
             dl.getDescendantCoordRelativeToSelf(mTaskBeingDragged, mTempCords);
             mEndDisplacement = dl.getHeight() - mTempCords[1];
         }
+        mEndDisplacement *= verticalFactor;
 
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setOnCancelRunnable(null);
@@ -220,9 +227,10 @@
     }
 
     @Override
-    public void onDragStart(boolean start) {
+    public void onDragStart(boolean start, float startDisplacement) {
+        PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
         if (mCurrentAnimation == null) {
-            reInitAnimationController(mDetector.wasInitialTouchPositive());
+            reInitAnimationController(orientationHandler.isGoingUp(startDisplacement));
             mDisplacementShift = 0;
         } else {
             mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
@@ -233,9 +241,10 @@
 
     @Override
     public boolean onDrag(float displacement) {
+        PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
         float totalDisplacement = displacement + mDisplacementShift;
-        boolean isGoingUp =
-                totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
+        boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp :
+                orientationHandler.isGoingUp(totalDisplacement);
         if (isGoingUp != mCurrentAnimationIsGoingUp) {
             reInitAnimationController(isGoingUp);
             mFlingBlockCheck.blockFling();
@@ -262,11 +271,12 @@
         if (blockedFling) {
             fling = false;
         }
+        PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
         float progress = mCurrentAnimation.getProgressFraction();
         float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
             logAction = Touch.FLING;
-            boolean goingUp = velocity < 0;
+            boolean goingUp = orientationHandler.isGoingUp(velocity);
             goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
         } else {
             logAction = Touch.SWIPE;
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 59b117f..375f160 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -125,7 +125,8 @@
             return anim;
         }
 
-        final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper(mActivity);
+        final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper(
+            mRecentsView.getPagedViewOrientedState(), mActivity);
 
         // At this point, the activity is already started and laid-out. Get the home-bounds
         // relative to the screen using the rootView of the activity.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 3601af2..f50bb3e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -45,7 +45,9 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.model.PagedViewOrientedState;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
@@ -94,7 +96,7 @@
     protected final BaseActivityInterface<T> mActivityInterface;
     protected final InputConsumerController mInputConsumer;
 
-    protected final AppWindowAnimationHelper mAppWindowAnimationHelper;
+    protected AppWindowAnimationHelper mAppWindowAnimationHelper;
     protected final TransformParams mTransformParams = new TransformParams();
 
     // Shift in the range of [0, 1].
@@ -123,6 +125,8 @@
     protected boolean mCanceled;
     protected int mFinishingRecentsAnimationForNewTaskId = -1;
 
+    private PagedViewOrientedState mOrientedState;
+
     protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             GestureState gestureState, InputConsumerController inputConsumer) {
         mContext = context;
@@ -132,20 +136,18 @@
         mActivityInitListener =
                 mActivityInterface.createActivityInitListener(this::onActivityInit);
         mInputConsumer = inputConsumer;
-
         mAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
         mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
-
         initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
-                .getDeviceProfile(mContext));
+            .getDeviceProfile(mContext));
     }
 
     protected void performHapticFeedback() {
         VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
     }
 
-    public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) {
-        return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null;
+    public Consumer<MotionEvent> getRecentsViewDispatcher() {
+        return mRecentsView != null ? mRecentsView.getEventDispatcher() : null;
     }
 
     @UiThread
@@ -326,10 +328,21 @@
             // we otherwise use the minimized home bounds provided by the system.
             mAppWindowAnimationHelper.updateHomeBounds(getStackBounds(dp));
         }
+        int displayRotation = 0;
+        if (mOrientedState != null) {
+            // TODO(b/150300347): The first recents animation after launcher is started with the
+            //  foreground app not in landscape will look funky until that bug is fixed
+            displayRotation = mOrientedState.getDisplayRotation();
+        }
+        RotationHelper.getTargetRectForRotation(TEMP_RECT, dp.widthPx, dp.heightPx,
+            displayRotation);
         mAppWindowAnimationHelper.updateTargetRect(TEMP_RECT);
         if (mDeviceState.isFullyGesturalNavMode()) {
             // We can drag all the way to the top of the screen.
-            mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
+            // TODO(b/149609070): Landscape apps are currently limited in
+            //   their ability to scale past the target rect.
+            float dragFactor = (float) dp.heightPx / mTransitionDragLength;
+            mDragLengthFactor = displayRotation == 0 ? dragFactor : Math.min(1.0f, dragFactor);
         }
     }
 
@@ -338,7 +351,17 @@
      */
     protected abstract boolean moveWindowWithRecentsScroll();
 
-    protected abstract boolean onActivityInit(Boolean alreadyOnHome);
+    protected boolean onActivityInit(Boolean alreadyOnHome) {
+        T createdActivity = mActivityInterface.getCreatedActivity();
+        if (createdActivity != null) {
+            mOrientedState = ((RecentsView) createdActivity.getOverviewPanel())
+                .getPagedViewOrientedState();
+            mAppWindowAnimationHelper = new AppWindowAnimationHelper(mOrientedState, mContext);
+            initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext)
+                .getDeviceProfile(mContext));
+        }
+        return true;
+    }
 
     /**
      * Called to create a input proxy for the running task
@@ -382,23 +405,29 @@
      */
     protected void applyTransformUnchecked() {
         float shift = mCurrentShift.value;
-        float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
-        float offsetScale = getTaskCurveScaleForOffsetX(offsetX,
-        mAppWindowAnimationHelper.getTargetRect().width());
+        float offset = mRecentsView == null ? 0 : mRecentsView.getScrollOffset();
+        float taskSize = getOrientationHandler()
+            .getPrimarySize(mAppWindowAnimationHelper.getTargetRect());
+        float offsetScale = getTaskCurveScaleForOffset(offset, taskSize);
         mTransformParams.setProgress(shift)
-                .setOffsetX(offsetX)
+                .setOffset(offset)
                 .setOffsetScale(offsetScale)
                 .setTargetSet(mRecentsAnimationTargets)
                 .setLauncherOnTop(true);
         mAppWindowAnimationHelper.applyTransform(mTransformParams);
     }
 
-    private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) {
-        float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing;
-        float interpolation = Math.min(1, offsetX / distanceToReachEdge);
+    private float getTaskCurveScaleForOffset(float offset, float taskSize) {
+        int dpPixel = getOrientationHandler().getShortEdgeLength(mDp);
+        float distanceToReachEdge = dpPixel / 2 + taskSize / 2 + mPageSpacing;
+        float interpolation = Math.min(1, offset / distanceToReachEdge);
         return TaskView.getCurveScaleForInterpolation(interpolation);
     }
 
+    protected PagedOrientationHandler getOrientationHandler() {
+        return mOrientedState.getOrientationHandler();
+    }
+
     /**
      * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
@@ -406,15 +435,18 @@
      */
     protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
             HomeAnimationFactory homeAnimationFactory) {
-        final RectF startRect = new RectF(
-                mAppWindowAnimationHelper.applyTransform(
-                        mTransformParams.setProgress(startProgress)
-                                .setTargetSet(mRecentsAnimationTargets)
-                                .setLauncherOnTop(false)));
         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
-
         final View floatingView = homeAnimationFactory.getFloatingView();
         final boolean isFloatingIconView = floatingView instanceof FloatingIconView;
+        final RectF startRect = new RectF(
+            mAppWindowAnimationHelper.applyTransform(
+                mTransformParams.setProgress(startProgress)
+                    .setTargetSet(mRecentsAnimationTargets)
+                    .setLauncherOnTop(false)));
+        if (isFloatingIconView) {
+            RotationHelper.mapInverseRectFromNormalOrientation(startRect,
+                mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
+        }
         RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext.getResources());
         if (isFloatingIconView) {
             FloatingIconView fiv = (FloatingIconView) floatingView;
@@ -435,6 +467,7 @@
         // We want the window alpha to be 0 once this threshold is met, so that the
         // FolderIconView can be seen morphing into the icon shape.
         final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f;
+        final RectF rotatedRect = new RectF();
         anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() {
 
             @Override
@@ -445,9 +478,12 @@
                         Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
                         .setCurrentRect(currentRect)
                         .setTargetAlpha(getWindowAlpha(progress));
+                rotatedRect.set(currentRect);
                 if (isFloatingIconView) {
+                    RotationHelper.mapRectFromNormalOrientation(rotatedRect,
+                        mDp.widthPx, mDp.heightPx, mOrientedState.getDisplayRotation());
                     mTransformParams.setCornerRadius(endRadius * progress + startRadius
-                            * (1f - progress));
+                        * (1f - progress));
                 }
                 mAppWindowAnimationHelper.applyTransform(mTransformParams);
 
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 6574d22..71580ca 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -30,10 +30,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.util.ActivityInitListener;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index cd001a1..65fba08 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -161,12 +161,12 @@
 
     @Override
     protected boolean onActivityInit(Boolean alreadyOnHome) {
+        super.onActivityInit(alreadyOnHome);
         mActivity = mActivityInterface.getCreatedActivity();
         mRecentsView = mActivity.getOverviewPanel();
         linkRecentsViewScroll();
         mRecentsView.setDisallowScrollToClearAll(true);
         mRecentsView.getClearAllButton().setVisibilityAlpha(0);
-
         mRecentsView.setZoomProgress(1);
 
         if (!mContinuingLastGesture) {
@@ -177,6 +177,7 @@
             }
         }
         mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
+        mDeviceState.enableMultipleRegions(false);
         return true;
     }
 
@@ -419,7 +420,7 @@
                         this::createNewInputProxyHandler);
                 RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
                 anim.addAnimatorListener(endListener);
-                anim.start(mEndVelocityPxPerMs);
+                anim.start(mContext, mEndVelocityPxPerMs);
                 mFinishAnimation = RunningWindowAnim.wrap(anim);
             } else {
 
@@ -469,7 +470,8 @@
         HomeAnimationFactory factory = new HomeAnimationFactory() {
             @Override
             public RectF getWindowTargetRect() {
-                return HomeAnimationFactory.getDefaultWindowTargetRect(mDp);
+                return HomeAnimationFactory
+                    .getDefaultWindowTargetRect(mRecentsView.getPagedOrientationHandler(), mDp);
             }
 
             @Override
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 1b6d291..f19ec69 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -54,6 +54,8 @@
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -176,7 +178,8 @@
                 if (canUseWorkspaceView) {
                     return iconLocation;
                 } else {
-                    return HomeAnimationFactory.getDefaultWindowTargetRect(dp);
+                    return HomeAnimationFactory
+                        .getDefaultWindowTargetRect(recentsView.getPagedOrientationHandler(), dp);
                 }
             }
 
@@ -268,23 +271,28 @@
                     float scrollOffsetX = recentsView.getScrollOffset();
                     float offscreenX = recentsView.getOffscreenTranslationX(currScale);
 
-                    float fromTranslationX = attached ? offscreenX - scrollOffsetX : 0;
-                    float toTranslationX = attached ? 0 : offscreenX - scrollOffsetX;
+                    float fromTranslation = attached ? offscreenX - scrollOffsetX : 0;
+                    float toTranslation = attached ? 0 : offscreenX - scrollOffsetX;
                     launcher.getStateManager()
                             .cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
 
+                    PagedOrientationHandler pagedOrientationHandler =
+                        recentsView.getPagedViewOrientedState().getOrientationHandler();
                     if (!recentsView.isShown() && animate) {
-                        recentsView.setTranslationX(fromTranslationX);
+                        pagedOrientationHandler
+                            .getPrimaryViewTranslate().set(recentsView, fromTranslation);
                     } else {
-                        fromTranslationX = recentsView.getTranslationX();
+                        fromTranslation =
+                            pagedOrientationHandler.getPrimaryViewTranslate().get(recentsView);
                     }
 
                     if (!animate) {
-                        recentsView.setTranslationX(toTranslationX);
+                        pagedOrientationHandler
+                            .getPrimaryViewTranslate().set(recentsView, toTranslation);
                     } else {
                         launcher.getStateManager().createStateElementAnimation(
                                 INDEX_RECENTS_TRANSLATE_X_ANIM,
-                                fromTranslationX, toTranslationX).start();
+                                fromTranslation, toTranslation).start();
                     }
 
                     fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 646d01f..3807e45 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -261,6 +261,7 @@
 
     @Override
     protected boolean onActivityInit(Boolean alreadyOnHome) {
+        super.onActivityInit(alreadyOnHome);
         final T activity = mActivityInterface.getCreatedActivity();
         if (mActivity == activity) {
             return true;
@@ -770,6 +771,16 @@
             }
         }
 
+        if (endTarget == NEW_TASK) {
+            SystemUiProxy.INSTANCE.get(mContext).onQuickSwitchToNewTask();
+        }
+
+        if (endTarget == RECENTS || endTarget == HOME) {
+            // Since we're now done quickStepping, we want to only listen for touch events
+            // for the main orientation's nav bar, instead of multiple
+            mDeviceState.enableMultipleRegions(false);
+        }
+
         if (mDeviceState.isOverviewDisabled() && (endTarget == RECENTS || endTarget == LAST_TASK)) {
             return LAST_TASK;
         }
@@ -929,7 +940,8 @@
                     mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
                 }
             });
-            windowAnim.start(velocityPxPerMs);
+            getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
+            windowAnim.start(mContext, velocityPxPerMs);
             homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
             mLauncherTransitionController = null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index 3f5179f..94b0051 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -183,7 +183,8 @@
             RemoteAnimationTargetCompat[] wallpaperTargets) {
         AnimatorSet target = new AnimatorSet();
         boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
-        AppWindowAnimationHelper helper = new AppWindowAnimationHelper(this);
+        AppWindowAnimationHelper helper = new AppWindowAnimationHelper(
+            mFallbackRecentsView.getPagedViewOrientedState(), this);
         target.play(getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
                 wallpaperTargets, helper).setDuration(RECENTS_LAUNCH_DURATION));
 
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 28e8fb6..ffa17ce 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -17,13 +17,7 @@
 
 import static android.view.MotionEvent.ACTION_DOWN;
 
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
-import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
@@ -56,6 +50,8 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.config.FeatureFlags;
@@ -80,6 +76,7 @@
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.OverscrollPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -449,6 +446,8 @@
 
         Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
+        mDeviceState.setOrientationTransformIfNeeded(event);
+
         if (event.getAction() == ACTION_DOWN) {
             GestureState newGestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.generateAndSetLogId());
@@ -502,7 +501,9 @@
                 || previousGestureState.isRecentsAnimationRunning()
                         ? newBaseConsumer(previousGestureState, newGestureState, event)
                         : mResetGestureInputConsumer;
+        // TODO(b/149880412): 2 button landscape mode is wrecked. Fixit!
         if (mDeviceState.isFullyGesturalNavMode()) {
+            handleOrientationSetup(base);
             if (mDeviceState.canTriggerAssistantAction(event)) {
                 base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
             }
@@ -533,6 +534,22 @@
         return base;
     }
 
+    private void handleOrientationSetup(InputConsumer baseInputConsumer) {
+        if (!PagedView.sFlagForcedRotation) {
+            return;
+        }
+        mDeviceState.enableMultipleRegions(baseInputConsumer instanceof OtherActivityInputConsumer);
+        Launcher l = (Launcher) mOverviewComponentObserver
+            .getActivityInterface().getCreatedActivity();
+        if (l == null || !(l.getOverviewPanel() instanceof RecentsView)) {
+            return;
+        }
+        ((RecentsView)l.getOverviewPanel())
+            .setLayoutRotation(mDeviceState.getCurrentActiveRotation(),
+                mDeviceState.getDisplayRotation());
+        l.getDragLayer().recreateControllers();
+    }
+
     private InputConsumer newBaseConsumer(GestureState previousGestureState,
             GestureState gestureState, MotionEvent event) {
         if (mDeviceState.isKeyguardShowingOccluded()) {
@@ -732,10 +749,18 @@
             FeatureFlags.dump(pw);
             PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
             mDeviceState.dump(pw);
-            mOverviewComponentObserver.dump(pw);
+            if (mOverviewComponentObserver != null) {
+                mOverviewComponentObserver.dump(pw);
+            }
+            if (mGestureState != null) {
+                mGestureState.dump(pw);
+            }
             pw.println("TouchState:");
+            BaseDraggingActivity createdOverviewActivity = mOverviewComponentObserver == null ? null
+                    : mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
             boolean resumed = mOverviewComponentObserver != null
                     && mOverviewComponentObserver.getActivityInterface().isResumed();
+            pw.println("  createdOverviewActivity=" + createdOverviewActivity);
             pw.println("  resumed=" + resumed);
             pw.println("  mConsumer=" + mConsumer.getName());
             ActiveGestureLog.INSTANCE.dump("", pw);
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 79e71a1..dc0c194 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
@@ -195,4 +195,9 @@
         }
         super.applyLoadPlan(tasks);
     }
+
+    @Override
+    protected boolean supportsVerticalLandscape() {
+        return false;
+    }
 }
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 8e7074d..bd9f330 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
@@ -176,8 +176,7 @@
         // Proxy events to recents view
         if (mPassedWindowMoveSlop && mInteractionHandler != null
                 && !mRecentsViewDispatcher.hasConsumer()) {
-            mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher(
-                    mNavBarPosition.getRotationMode()));
+            mRecentsViewDispatcher.setConsumer(mInteractionHandler.getRecentsViewDispatcher());
         }
         int edgeFlags = ev.getEdgeFlags();
         ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR);
@@ -364,7 +363,6 @@
                         : mNavBarPosition.isLeftEdge()
                                 ? -velocityX
                                 : velocityY;
-
                 mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
                 mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY),
                         mDownPos);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 5a9c2fe..91af156 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -15,12 +15,6 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
-import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
-
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Matrix;
@@ -28,6 +22,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
+import android.view.Surface;
 
 import androidx.annotation.Nullable;
 
@@ -37,6 +32,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.model.PagedViewOrientedState;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.SystemUiProxy;
@@ -50,6 +46,12 @@
 import com.android.systemui.shared.system.TransactionCompat;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
+import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
+
 /**
  * Utility class to handle window clip animation
  */
@@ -82,6 +84,7 @@
     private final Rect mTmpRect = new Rect();
     private final RectF mTmpRectF = new RectF();
     private final RectF mCurrentRectWithInsets = new RectF();
+    private PagedViewOrientedState mOrientedState;
     // Corner radius of windows, in pixels
     private final float mWindowCornerRadius;
     // Corner radius of windows when they're in overview mode.
@@ -100,20 +103,24 @@
     private TargetAlphaProvider mTaskAlphaCallback = (t, a) -> a;
     private TargetAlphaProvider mBaseAlphaCallback = (t, a) -> 1;
 
-    public AppWindowAnimationHelper(Context context) {
+    public AppWindowAnimationHelper(PagedViewOrientedState orientedState, Context context) {
+        mOrientedState = orientedState;
         mWindowCornerRadius = getWindowCornerRadius(context.getResources());
         mSupportsRoundedCornersOnWindows = supportsRoundedCornersOnWindows(context.getResources());
         mTaskCornerRadius = TaskCornerRadius.get(context);
         mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows;
     }
 
+    public AppWindowAnimationHelper(Context context) {
+        this(null, context);
+    }
+
     private void updateSourceStack(RemoteAnimationTargetCompat target) {
         mSourceInsets.set(target.contentInsets);
         mSourceStackBounds.set(target.sourceContainerBounds);
 
         // TODO: Should sourceContainerBounds already have this offset?
         mSourceStackBounds.offsetTo(target.position.x, target.position.y);
-
     }
 
     public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
@@ -138,8 +145,9 @@
         // from the source rect. The difference between the target rect (scaled to the
         // source rect) is the amount to clip on each edge.
         RectF scaledTargetRect = new RectF(mTargetRect);
-        Utilities.scaleRectFAboutCenter(scaledTargetRect,
-                mSourceRect.width() / mTargetRect.width());
+        float scale = getSrcToTargetScale();
+        Utilities.scaleRectFAboutCenter(scaledTargetRect, scale);
+
         scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
         mSourceWindowClipInsets.set(
                 Math.max(scaledTargetRect.left, 0),
@@ -149,6 +157,16 @@
         mSourceRect.set(scaledTargetRect);
     }
 
+    private float getSrcToTargetScale() {
+        if (mOrientedState == null ||
+            (mOrientedState.getDisplayRotation() == Surface.ROTATION_0
+                || mOrientedState.getDisplayRotation() == Surface.ROTATION_180)) {
+            return mSourceRect.width() / mTargetRect.width();
+        } else {
+            return mSourceRect.height() / mTargetRect.height();
+        }
+    }
+
     public void prepareAnimation(DeviceProfile dp, boolean isOpening) {
         mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
         mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows && !dp.isMultiWindowMode;
@@ -221,7 +239,6 @@
                     layer = Integer.MAX_VALUE;
                 }
             }
-
             // Since radius is in Surface space, but we draw the rounded corners in screen space, we
             // have to undo the scale.
             surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer,
@@ -237,11 +254,16 @@
             mTmpRectF.set(mTargetRect);
             Utilities.scaleRectFAboutCenter(mTmpRectF, params.mOffsetScale);
             mCurrentRect.set(mRectFEvaluator.evaluate(params.mProgress, mSourceRect, mTmpRectF));
-            mCurrentRect.offset(params.mOffsetX, 0);
+            if (mOrientedState == null || mOrientedState.areMultipleLayoutOrientationsDisabled()) {
+                mCurrentRect.offset(params.mOffset, 0);
+            } else {
+                int displayRotation = mOrientedState.getDisplayRotation();
+                mOrientedState.getOrientationHandler().offsetTaskRect(mCurrentRect,
+                    params.mOffset, displayRotation);
+            }
         }
 
         updateClipRect(params);
-
         return mCurrentRect;
     }
 
@@ -340,7 +362,7 @@
      * @return The source rect's scale and translation relative to the target rect.
      */
     public LauncherState.ScaleAndTranslation getScaleAndTranslation() {
-        float scale = mSourceRect.width() / mTargetRect.width();
+        float scale = getSrcToTargetScale();
         float translationY = mSourceRect.centerY() - mSourceRect.top - mTargetRect.centerY();
         return new LauncherState.ScaleAndTranslation(scale, 0, translationY);
     }
@@ -390,7 +412,7 @@
 
     public static class TransformParams {
         private float mProgress;
-        private float mOffsetX;
+        private float mOffset;
         private float mOffsetScale;
         private @Nullable RectF mCurrentRect;
         private float mTargetAlpha;
@@ -401,7 +423,7 @@
 
         public TransformParams() {
             mProgress = 0;
-            mOffsetX = 0;
+            mOffset = 0;
             mOffsetScale = 1;
             mCurrentRect = null;
             mTargetAlpha = 1;
@@ -453,8 +475,8 @@
          * the default), then offset the current rect by this amount after computing the rect based
          * on {@link #mProgress}.
          */
-        public TransformParams setOffsetX(float offsetX) {
-            mOffsetX = offsetX;
+        public TransformParams setOffset(float offset) {
+            mOffset = offset;
             return this;
         }
 
@@ -504,8 +526,8 @@
             return mProgress;
         }
 
-        public float getOffsetX() {
-            return mOffsetX;
+        public float getOffset() {
+            return mOffset;
         }
 
         public float getOffsetScale() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
index a59c99c..682c92c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.util;
 
 import android.animation.Animator;
+import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.PointF;
 import android.graphics.RectF;
@@ -28,6 +29,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.FlingSpringAnim;
+import com.android.launcher3.util.DynamicResource;
+import com.android.systemui.plugins.ResourceProvider;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -137,7 +140,12 @@
         mAnimatorListeners.add(animatorListener);
     }
 
-    public void start(PointF velocityPxPerMs) {
+    /**
+     * Starts the fling/spring animation.
+     * @param context The activity context.
+     * @param velocityPxPerMs Velocity of swipe in px/ms.
+     */
+    public void start(Context context, PointF velocityPxPerMs) {
         // Only tell caller that we ended if both x and y animations have ended.
         OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> {
             mRectXAnimEnded = true;
@@ -166,10 +174,14 @@
                 mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener);
 
         float minVisibleChange = Math.abs(1f / mStartRect.height());
+        ResourceProvider rp = DynamicResource.provider(context);
+        float damping = rp.getFloat(R.dimen.swipe_up_rect_damping_ratio);
+        float stiffness = rp.getFloat(R.dimen.swipe_up_rect_stiffness);
+
         mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS)
                 .setSpring(new SpringForce(1f)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
-                .setStiffness(SpringForce.STIFFNESS_LOW))
+                .setDampingRatio(damping)
+                .setStiffness(stiffness))
                 .setStartVelocity(velocityPxPerMs.y * minVisibleChange)
                 .setMaxValue(1f)
                 .setMinimumVisibleChange(minVisibleChange)
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 babf13e..d2805ed 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
@@ -41,7 +41,9 @@
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.util.DynamicResource;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.plugins.ResourceProvider;
 
 /**
  * Creates an animation where all the workspace items are moved into their final location,
@@ -55,9 +57,6 @@
 
     private static final float MAX_VELOCITY_PX_PER_S = 22f;
 
-    private static final float DAMPING_RATIO = 0.7f;
-    private static final float STIFFNESS = 150f;
-
     private final float mVelocity;
     private final float mSpringTransY;
 
@@ -177,9 +176,12 @@
 
         v.setTranslationY(mSpringTransY);
 
+        ResourceProvider rp = DynamicResource.provider(v.getContext());
+        float stiffness = rp.getFloat(R.dimen.staggered_stiffness);
+        float damping = rp.getFloat(R.dimen.staggered_damping_ratio);
         ObjectAnimator springTransY = new SpringAnimationBuilder<>(v, VIEW_TRANSLATE_Y)
-                .setStiffness(STIFFNESS)
-                .setDampingRatio(DAMPING_RATIO)
+                .setStiffness(stiffness)
+                .setDampingRatio(damping)
                 .setMinimumVisibleChange(1f)
                 .setEndValue(0)
                 .setStartVelocity(mVelocity)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
index 9db0c09..d0819c1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
@@ -21,7 +21,7 @@
 import android.util.Property;
 import android.widget.Button;
 
-import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
 import com.android.quickstep.views.RecentsView.ScrollState;
 
@@ -44,21 +44,26 @@
     private float mContentAlpha = 1;
     private float mVisibilityAlpha = 1;
 
-    private final boolean mIsRtl;
+    private boolean mIsRtl;
 
     private int mScrollOffset;
+    private RecentsView mParent;
 
     public ClearAllButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mIsRtl = Utilities.isRtl(context.getResources());
     }
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
+        mScrollOffset = mIsRtl ? mParent.getPaddingRight() / 2 : - mParent.getPaddingLeft() / 2;
+    }
 
-        RecentsView parent = (RecentsView) getParent();
-        mScrollOffset = mIsRtl ? parent.getPaddingRight() / 2 : - parent.getPaddingLeft() / 2;
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mParent = (RecentsView) getParent();
+        mIsRtl = !mParent.getPagedOrientationHandler().getRecentsRtlSetting(getResources());
     }
 
     @Override
@@ -73,6 +78,21 @@
         }
     }
 
+    public void onLayoutChanged() {
+        if (mParent == null) {
+            return;
+        }
+        setRotation(mParent.getPagedOrientationHandler().getDegreesRotated());
+    }
+
+    public void setRtl(boolean rtl) {
+        if (mIsRtl == rtl) {
+            return;
+        }
+        mIsRtl = rtl;
+        invalidate();
+    }
+
     public void setVisibilityAlpha(float alpha) {
         if (mVisibilityAlpha != alpha) {
             mVisibilityAlpha = alpha;
@@ -82,14 +102,16 @@
 
     @Override
     public void onPageScroll(ScrollState scrollState) {
-        float width = getWidth();
-        if (width == 0) {
+        PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler();
+        float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
+        if (orientationSize == 0) {
             return;
         }
 
-        float shift = Math.min(scrollState.scrollFromEdge, width);
-        setTranslationX(mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift));
-        mScrollAlpha = 1 - shift / width;
+        float shift = Math.min(scrollState.scrollFromEdge, orientationSize);
+        float translation = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift);
+        orientationHandler.setPrimaryAndResetSecondaryTranslate(this, translation);
+        mScrollAlpha = 1 - shift / orientationSize;
         updateAlpha();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 3e106aa..b2d182b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
+import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
@@ -180,19 +181,21 @@
      * @return The translationX to apply to this view so that the first task is just offscreen.
      */
     public float getOffscreenTranslationX(float recentsScale) {
-        float offscreenX = NORMAL.getOverviewScaleAndTranslation(mActivity).translationX;
+        LauncherState.ScaleAndTranslation overviewScaleAndTranslation =
+            NORMAL.getOverviewScaleAndTranslation(mActivity);
+        float offscreen = mOrientationHandler.getTranslationValue(overviewScaleAndTranslation);
         // Offset since scale pushes tasks outwards.
         getTaskSize(sTempRect);
-        int taskWidth = sTempRect.width();
-        offscreenX += taskWidth * (recentsScale - 1) / 2;
+        int taskSize = mOrientationHandler.getPrimarySize(sTempRect);
+        offscreen += taskSize * (recentsScale - 1) / 2;
         if (mRunningTaskTileHidden) {
             // The first task is hidden, so offset by its width.
-            offscreenX -= (taskWidth + getPageSpacing()) * recentsScale;
+            offscreen -= (taskSize + getPageSpacing()) * recentsScale;
         }
         if (isRtl()) {
-            offscreenX = -offscreenX;
+            offscreen = -offscreen;
         }
-        return offscreenX;
+        return offscreen;
     }
 
     @Override
@@ -277,6 +280,11 @@
     }
 
     @Override
+    protected boolean supportsVerticalLandscape() {
+        return PagedView.sFlagForcedRotation;
+    }
+
+    @Override
     public void reset() {
         super.reset();
 
@@ -339,19 +347,19 @@
     }
 
     @Override
-    protected int computeMinScrollX() {
+    protected int computeMinScroll() {
         if (canComputeScrollX() && !mIsRtl) {
             return computeScrollX();
         }
-        return super.computeMinScrollX();
+        return super.computeMinScroll();
     }
 
     @Override
-    protected int computeMaxScrollX() {
+    protected int computeMaxScroll() {
         if (canComputeScrollX() && mIsRtl) {
             return computeScrollX();
         }
-        return super.computeMaxScrollX();
+        return super.computeMaxScroll();
     }
 
     private boolean canComputeScrollX() {
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 27ef93c..c201455 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
@@ -22,7 +22,6 @@
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
@@ -52,7 +51,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Canvas;
-import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -66,11 +64,13 @@
 import android.text.TextPaint;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.Property;
 import android.util.SparseBooleanArray;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.OrientationEventListener;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -80,7 +80,6 @@
 import android.widget.ListView;
 
 import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
@@ -90,16 +89,20 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.anim.SpringObjectAnimator;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.PendingAnimation;
 import com.android.launcher3.util.Themes;
@@ -114,6 +117,7 @@
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.LayoutUtils;
+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.ThumbnailData;
@@ -162,6 +166,8 @@
                 }
             };
 
+    private final OrientationEventListener mOrientationListener;
+    private int mPreviousRotation;
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
     protected AppWindowAnimationHelper mAppWindowAnimationHelper;
@@ -268,15 +274,8 @@
         }
     };
 
-    private final IPinnedStackAnimationListener mIPinnedStackAnimationListener =
-            new IPinnedStackAnimationListener.Stub() {
-        @Override
-        public void onPinnedStackAnimationStarted() {
-            // Needed for activities that auto-enter PiP, which will not trigger a remote
-            // animation to be created
-            mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
-        }
-    };
+    private final PinnedStackAnimationListener mIPinnedStackAnimationListener =
+            new PinnedStackAnimationListener();
 
     // Used to keep track of the last requested task list id, so that we do not request to load the
     // tasks again if we have already requested it and the task list has not changed
@@ -340,7 +339,8 @@
         mActivity = (T) BaseActivity.fromContext(context);
         mModel = RecentsModel.INSTANCE.get(context);
         mIdp = InvariantDeviceProfile.INSTANCE.get(context);
-        mTempAppWindowAnimationHelper = new AppWindowAnimationHelper(context);
+        mTempAppWindowAnimationHelper =
+            new AppWindowAnimationHelper(getPagedViewOrientedState(), context);
 
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
@@ -348,7 +348,7 @@
         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
                 10 /* initial size */);
 
-        mIsRtl = !Utilities.isRtl(getResources());
+        mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         mTaskTopMargin = getResources()
                 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
@@ -368,9 +368,21 @@
                 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
         setWillNotDraw(false);
         updateEmptyMessage();
+        disableMultipleLayoutRotations(!supportsVerticalLandscape());
 
         // Initialize quickstep specific cache params here, as this is constructed only once
         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
+        mOrientationListener = new OrientationEventListener(getContext()) {
+            @Override
+            public void onOrientationChanged(int i) {
+                int rotation = RotationHelper.getRotationFromDegrees(i);
+                if (mPreviousRotation != rotation) {
+                    animateRecentsRotationInPlace(rotation);
+                    mPreviousRotation = rotation;
+                }
+            }
+        };
+
     }
 
     public OverScroller getScroller() {
@@ -451,6 +463,7 @@
         mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this);
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIdp.addOnChangeListener(this);
+        mIPinnedStackAnimationListener.setActivity(mActivity);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(
                 mIPinnedStackAnimationListener);
     }
@@ -466,6 +479,7 @@
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
         mIdp.removeOnChangeListener(this);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
+        mIPinnedStackAnimationListener.setActivity(null);
     }
 
     @Override
@@ -496,6 +510,13 @@
     }
 
     public void setOverviewStateEnabled(boolean enabled) {
+        if (supportsVerticalLandscape() && mOrientationListener.canDetectOrientation()) {
+            if (enabled) {
+                mOrientationListener.enable();
+            } else {
+                mOrientationListener.disable();
+            }
+        }
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
         if (!enabled) {
@@ -623,7 +644,7 @@
             final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
             final Task task = tasks.get(i);
             final TaskView taskView = (TaskView) getChildAt(pageIndex);
-            taskView.bind(task);
+            taskView.bind(task, mLayoutRotation);
         }
 
         if (mNextPage == INVALID_PAGE) {
@@ -755,19 +776,21 @@
         if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) {
             return;
         }
-        int scrollX = getScrollX();
-        final int halfPageWidth = getNormalChildWidth() / 2;
-        final int screenCenter = mInsets.left + getPaddingLeft() + scrollX + halfPageWidth;
-        final int halfScreenWidth = getMeasuredWidth() / 2;
+        CurveProperties curveProperties = mOrientationHandler
+            .getCurveProperties(this, mInsets);
+        int scroll = curveProperties.scroll;
+        final int halfPageSize = curveProperties.halfPageSize;
+        final int screenCenter = curveProperties.screenCenter;
+        final int halfScreenSize = curveProperties.halfScreenSize;
         final int pageSpacing = mPageSpacing;
-        mScrollState.scrollFromEdge = mIsRtl ? scrollX : (mMaxScrollX - scrollX);
+        mScrollState.scrollFromEdge = mIsRtl ? scroll : (mMaxScroll - scroll);
 
         final int pageCount = getPageCount();
         for (int i = 0; i < pageCount; i++) {
             View page = getPageAt(i);
-            float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth;
+            float pageCenter = mOrientationHandler.getViewCenterPosition(page) + halfPageSize;
             float distanceFromScreenCenter = screenCenter - pageCenter;
-            float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing;
+            float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
             mScrollState.linearInterpolation = Math.min(1,
                     Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
             ((PageCallbacks) page).onPageScroll(mScrollState);
@@ -915,6 +938,47 @@
         setSwipeDownShouldLaunchApp(true);
     }
 
+    private void animateRecentsRotationInPlace(int newRotation) {
+        if (!supportsVerticalLandscape()) {
+            return;
+        }
+
+        AnimatorSet pa = setRecentsChangedOrientation(true);
+        pa.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                updateLayoutRotation(newRotation);
+                ((DragLayer)mActivity.getDragLayer()).recreateControllers();
+                rotateAllChildTasks();
+                setRecentsChangedOrientation(false).start();
+            }
+        });
+        pa.start();
+    }
+
+    public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) {
+        getRunningTaskIndex();
+        int runningIndex = getCurrentPage();
+        AnimatorSet as = new AnimatorSet();
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            if (runningIndex == i) {
+                continue;
+            }
+            View taskView = getTaskViewAt(i);
+            as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
+        }
+        return as;
+    }
+
+    abstract protected boolean supportsVerticalLandscape();
+
+    private void rotateAllChildTasks() {
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            TaskView taskView = getTaskViewAt(i);
+            taskView.setOverviewRotation(mLayoutRotation);
+        }
+    }
+
     /**
      * Called when a gesture from an app has finished.
      */
@@ -950,7 +1014,7 @@
                     new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0,
                     false, true, false, false, new ActivityManager.TaskDescription(), 0,
                     new ComponentName("", ""), false);
-            taskView.bind(mTmpRunningTask);
+            taskView.bind(mTmpRunningTask, mLayoutRotation);
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -1126,15 +1190,22 @@
 
     private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
         addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
+        FloatProperty<View> secondaryViewTranslate =
+            mOrientationHandler.getSecondaryViewTranslate();
+        int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
+        int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor();
         if (UNSTABLE_SPRINGS.get() && taskView instanceof TaskView) {
-            addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y,
-                            MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
-                            SpringForce.STIFFNESS_MEDIUM,
-                            0, -taskView.getHeight()),
+            ResourceProvider rp = DynamicResource.provider(mActivity);
+            float dampingRatio = rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio);
+            float stiffness = rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness);
+
+            addAnim(new SpringObjectAnimator<>(taskView, secondaryViewTranslate,
+                            MIN_VISIBLE_CHANGE_PIXELS, dampingRatio,
+                            stiffness, 0, verticalFactor * secondaryTaskDimension),
                     duration, LINEAR, anim);
         } else {
-            addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
-                    duration, LINEAR, anim);
+            addAnim(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
+                verticalFactor * secondaryTaskDimension), duration, LINEAR, anim);
         }
     }
 
@@ -1165,11 +1236,9 @@
         }
 
         int[] oldScroll = new int[count];
-        getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
-
         int[] newScroll = new int[count];
+        getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC);
         getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
-
         int taskCount = getTaskViewCount();
         int scrollDiffPerPage = 0;
         if (count > 1) {
@@ -1207,13 +1276,17 @@
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
                     if (UNSTABLE_SPRINGS.get() && child instanceof TaskView) {
+                        ResourceProvider rp = DynamicResource.provider(mActivity);
+                        float damping = rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio);
+                        float stiffness = rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness);
+
                         addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X,
-                                MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
-                                SpringForce.STIFFNESS_MEDIUM,
-                                0, scrollDiff), duration, ACCEL, anim);
+                                MIN_VISIBLE_CHANGE_PIXELS, damping,
+                                stiffness, 0, scrollDiff), duration, ACCEL, anim);
                     } else {
-                        addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration,
-                                ACCEL, anim);
+                        Property translationProperty = mOrientationHandler.getPrimaryViewTranslate();
+                        addAnim(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff),
+                            duration, ACCEL, anim);
                     }
 
                     needsCurveUpdates = true;
@@ -1430,6 +1503,18 @@
     }
 
     @Override
+    public void setLayoutRotation(int touchRotation, int displayRotation) {
+        if (!sFlagForcedRotation) {
+            return;
+        }
+
+        super.setLayoutRotation(touchRotation, displayRotation);
+        mClearAllButton.onLayoutChanged();
+        mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
+        setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
+    }
+
+    @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         child.setAlpha(mContentAlpha);
@@ -1649,7 +1734,8 @@
             }
         });
 
-        AppWindowAnimationHelper appWindowAnimationHelper = new AppWindowAnimationHelper(mActivity);
+        AppWindowAnimationHelper appWindowAnimationHelper = new AppWindowAnimationHelper(
+            getPagedViewOrientedState(), mActivity);
         appWindowAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this);
         appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
@@ -1806,7 +1892,7 @@
     }
 
     @Override
-    protected int computeMinScrollX() {
+    protected int computeMinScroll() {
         if (getTaskViewCount() > 0) {
             if (mDisallowScrollToClearAll) {
                 // We aren't showing the clear all button,
@@ -1821,11 +1907,11 @@
             }
             return getScrollForPage(mTaskViewStartIndex);
         }
-        return super.computeMinScrollX();
+        return super.computeMinScroll();
     }
 
     @Override
-    protected int computeMaxScrollX() {
+    protected int computeMaxScroll() {
         if (getTaskViewCount() > 0) {
             if (mDisallowScrollToClearAll) {
                 // We aren't showing the clear all button,
@@ -1840,7 +1926,7 @@
             }
             return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1);
         }
-        return super.computeMaxScrollX();
+        return super.computeMaxScroll();
     }
 
     public ClearAllButton getClearAllButton() {
@@ -1855,31 +1941,25 @@
             return 0;
         }
         int startScroll = getScrollForPage(getRunningTaskIndex());
-        int offsetX = startScroll - getScrollX();
-        offsetX *= getScaleX();
+        int offsetX = startScroll - mOrientationHandler.getPrimaryScroll(this);
+        offsetX *= mOrientationHandler.getPrimaryScale(this);
         return offsetX;
     }
 
-    public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) {
-        if (rotationMode.isTransposed) {
-            Matrix transform = new Matrix();
-            transform.setRotate(-rotationMode.surfaceRotation);
-
-            if (getWidth() > 0 && getHeight() > 0) {
-                float scale = ((float) getWidth()) / getHeight();
-                transform.postScale(scale, 1 / scale);
-            }
-
-            Matrix inverse = new Matrix();
-            transform.invert(inverse);
-            return e -> {
-                e.transform(transform);
-                super.onTouchEvent(e);
-                e.transform(inverse);
-            };
-        } else {
+    public Consumer<MotionEvent> getEventDispatcher() {
+        int degreesRotated = RotationHelper.getDegreesFromRotation(mLayoutRotation);
+        if (degreesRotated == 0) {
             return super::onTouchEvent;
         }
+
+        // At this point the event coordinates have already been transformed, so we need to
+        // undo that transformation since PagedView also accommodates for the transformation via
+        // PagedOrientationHandler
+        return e -> {
+            RotationHelper.transformEvent(-degreesRotated, e, true);
+            super.onTouchEvent(e);
+            RotationHelper.transformEvent(-degreesRotated, e, false);
+        };
     }
 
     public AppWindowAnimationHelper getClipAnimationHelper() {
@@ -1962,4 +2042,20 @@
         /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */
         void onEmptyMessageUpdated(boolean isEmpty);
     }
+
+    private static class PinnedStackAnimationListener<T extends BaseActivity> extends
+            IPinnedStackAnimationListener.Stub {
+        private T mActivity;
+
+        public void setActivity(T activity) {
+            mActivity = activity;
+        }
+
+        @Override
+        public void onPinnedStackAnimationStarted() {
+            // Needed for activities that auto-enter PiP, which will not trigger a remote
+            // animation to be created
+            mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS);
+        }
+    }
 }
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 8ed1392..178ff32 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
@@ -36,6 +36,7 @@
 import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.Property;
 import android.view.Surface;
 import android.view.View;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 79b9a9d..9150cc7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -16,15 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static android.widget.Toast.LENGTH_SHORT;
-
-import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -41,7 +32,7 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
-import android.view.Gravity;
+import android.view.Surface;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -58,8 +49,10 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -83,6 +76,20 @@
 import java.util.List;
 import java.util.function.Consumer;
 
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.view.Gravity.CENTER_VERTICAL;
+import static android.view.Gravity.END;
+import static android.view.Gravity.START;
+import static android.view.Gravity.TOP;
+import static android.widget.Toast.LENGTH_SHORT;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+
 /**
  * A task in the Recents view.
  */
@@ -186,6 +193,8 @@
     private float mFooterVerticalOffset = 0;
     private float mFooterAlpha = 1;
     private int mStackHeight;
+    private boolean mHideActionsView;
+    private PagedOrientationHandler mOrientationHandler;
 
     public TaskView(Context context) {
         this(context, null);
@@ -244,7 +253,7 @@
             if (mActionsView != null) {
                 TaskView.LayoutParams params = new TaskView.LayoutParams(LayoutParams.MATCH_PARENT,
                         getResources().getDimensionPixelSize(R.dimen.overview_actions_height),
-                        Gravity.BOTTOM);
+                        BOTTOM);
                 addView(mActionsView, params);
                 mActionsView.setAlpha(0);
             }
@@ -266,10 +275,11 @@
     /**
      * Updates this task view to the given {@param task}.
      */
-    public void bind(Task task) {
+    public void bind(Task task, int recentsRotation) {
         cancelPendingLoadTasks();
         mTask = task;
         mSnapshotView.bind(task);
+        setOverviewRotation(recentsRotation);
     }
 
     public Task getTask() {
@@ -439,6 +449,45 @@
         }
     }
 
+    void setOverviewRotation(int iconRotation) {
+        PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
+        boolean isRtl = orientationHandler.getRecentsRtlSetting(getResources());
+        LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
+        snapshotParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getContext());
+        int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
+        LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
+        int rotation = RotationHelper.getDegreesFromRotation(iconRotation);
+        mHideActionsView = true;
+        switch (iconRotation) {
+            case Surface.ROTATION_90:
+                iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
+                iconParams.rightMargin = -thumbnailPadding;
+                iconParams.leftMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+                break;
+            case Surface.ROTATION_180:
+                iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+                iconParams.bottomMargin = -thumbnailPadding;
+                iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
+                break;
+            case Surface.ROTATION_270:
+                iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
+                iconParams.leftMargin = -thumbnailPadding;
+                iconParams.rightMargin = iconParams.topMargin = iconParams.bottomMargin = 0;
+                break;
+            case Surface.ROTATION_0:
+            default:
+                iconParams.gravity = TOP | CENTER_HORIZONTAL;
+                iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin =
+                    iconParams.bottomMargin = 0;
+                mHideActionsView = false;
+                break;
+        }
+        mSnapshotView.setLayoutParams(snapshotParams);
+        mIconView.setLayoutParams(iconParams);
+        mIconView.setRotation(rotation);
+        updateActionsViewVisibility(!mHideActionsView);
+    }
+
     private void setIconAndDimTransitionProgress(float progress, boolean invert) {
         if (invert) {
             progress = 1 - progress;
@@ -601,8 +650,7 @@
 
             addView(view, indexToAdd);
             LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
-            layoutParams.gravity =
-                    Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+            layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
             layoutParams.bottomMargin =
                     ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin;
             view.setAlpha(mFooterAlpha);
@@ -855,9 +903,7 @@
         mFullscreenProgress = progress;
         boolean isFullscreen = mFullscreenProgress > 0;
         mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
-        if (mActionsView != null) {
-            mActionsView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
-        }
+        updateActionsViewVisibility(progress < 1 && !mHideActionsView);
         setClipChildren(!isFullscreen);
         setClipToPadding(!isFullscreen);
 
@@ -891,6 +937,12 @@
         invalidateOutline();
     }
 
+    private void updateActionsViewVisibility(boolean isVisible) {
+        if (mActionsView != null) {
+            mActionsView.setVisibility(isVisible ? VISIBLE : GONE);
+        }
+    }
+
     public boolean isRunningTask() {
         if (getRecentsView() == null) {
             return false;
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 60cfa0c..f9bb2f2 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -31,7 +31,6 @@
         android:id="@+id/icon"
         android:layout_width="@dimen/task_thumbnail_icon_size"
         android:layout_height="@dimen/task_thumbnail_icon_size"
-        android:layout_gravity="top|center_horizontal"
         android:focusable="false"
         android:importantForAccessibility="no"/>
 </com.android.quickstep.views.TaskView>
\ No newline at end of file
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
new file mode 100644
index 0000000..53f37c1
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.quickstep;
+
+import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.util.DefaultDisplay;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class OrientationTouchTransformerTest {
+    private static final int SIZE_WIDTH = 1080;
+    private static final int SIZE_HEIGHT = 2280;
+    private static final float DENSITY_DISPLAY_METRICS = 3.0f;
+
+    private OrientationTouchTransformer mTouchTransformer;
+
+    Resources mResources;
+    private DefaultDisplay.Info mInfo;
+
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mResources = mock(Resources.class);
+        when(mResources.getBoolean(anyInt())).thenReturn(true);
+        when(mResources.getDimension(anyInt())).thenReturn(10.0f);
+        DisplayMetrics mockDisplayMetrics = new DisplayMetrics();
+        mockDisplayMetrics.density = DENSITY_DISPLAY_METRICS;
+        when(mResources.getDisplayMetrics()).thenReturn(mockDisplayMetrics);
+        mInfo = createDisplayInfo(Surface.ROTATION_0);
+        mTouchTransformer = new OrientationTouchTransformer(mResources, NO_BUTTON, () -> 0);
+    }
+
+    @Test
+    public void disabledMultipeRegions_shouldOverrideFirstRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
+        mTouchTransformer.createOrAddTouchRegion(info2);
+
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inOldRegion);
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+
+        // Override region
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        inOldRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inOldRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inOldRegion.getX(), inOldRegion.getY()));
+    }
+
+    @Test
+    public void allowMultipeRegions_shouldOverrideFirstRegion() {
+        DefaultDisplay.Info info2 = createDisplayInfo(Surface.ROTATION_90);
+        mTouchTransformer.createOrAddTouchRegion(info2);
+        // We have to add 0 rotation second so that gets set as the current rotation, otherwise
+        // matrix transform will fail (tests only work in Portrait at the moment)
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inNewRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inNewRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inNewRegion.getX(), inNewRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskNotFrozen_notInRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
+        mTouchTransformer.transform(outOfRegion);
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskFrozen_noRotate_outOfRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        MotionEvent outOfRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, 100);
+        mTouchTransformer.transform(outOfRegion);
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(outOfRegion.getX(), outOfRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskFrozen_noRotate_inRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskNotFrozen_noRotate_inDefaultRegion() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        float y = generateTouchRegionHeight(Surface.ROTATION_0) + 1;
+        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+    }
+
+    @Test
+    public void applyTransform_taskNotFrozen_90Rotate_inRegion() {
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        float y = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+        MotionEvent inRegion = generateMotionEvent(MotionEvent.ACTION_DOWN, 100, y);
+        mTouchTransformer.transform(inRegion);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion.getX(), inRegion.getY()));
+    }
+
+    @Test
+    @Ignore("There's too much that goes into needing to mock a real motion event so the "
+            + "transforms in native code get applied correctly. Once that happens then maybe we can"
+            + " write slightly more complex unit tests")
+    public void applyTransform_taskNotFrozen_90Rotate_inTwoRegions() {
+        mTouchTransformer.createOrAddTouchRegion(mInfo);
+        mTouchTransformer.enableMultipleRegions(true, mInfo);
+        mTouchTransformer.createOrAddTouchRegion(createDisplayInfo(Surface.ROTATION_90));
+        // Landscape point
+        float y1 = generateTouchRegionHeight(Surface.ROTATION_90) + 1;
+        MotionEvent inRegion1_down = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, y1);
+        MotionEvent inRegion1_up = generateMotionEvent(MotionEvent.ACTION_UP, 10, y1);
+        // Portrait point in landscape orientation axis
+        MotionEvent inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 10);
+        mTouchTransformer.transform(inRegion1_down);
+        mTouchTransformer.transform(inRegion2);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion1_down.getX(), inRegion1_down.getY()));
+        // We only process one gesture region until we see a MotionEvent.ACTION_UP
+        assertFalse(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
+
+        mTouchTransformer.transform(inRegion1_up);
+
+        // Set the new region with this MotionEvent.ACTION_DOWN
+        inRegion2 = generateMotionEvent(MotionEvent.ACTION_DOWN, 10, 370);
+        mTouchTransformer.transform(inRegion2);
+        assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
+    }
+
+    private DefaultDisplay.Info createDisplayInfo(int rotation) {
+        Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT);
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+            p = new Point(SIZE_HEIGHT, SIZE_WIDTH);
+        }
+        return new DefaultDisplay.Info(0, rotation, 0, p, p, p, null);
+    }
+
+    private float generateTouchRegionHeight(int rotation) {
+        float height = SIZE_HEIGHT;
+        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
+            height = SIZE_WIDTH;
+        }
+        return height - ResourceUtils.DEFAULT_NAVBAR_VALUE * DENSITY_DISPLAY_METRICS;
+    }
+
+    private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
+        return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 07d2381..fa0e840 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.uioverrides.BackButtonAlphaHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
 import com.android.launcher3.util.UiThreadHelper;
@@ -51,6 +52,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.views.RecentsView;
 
 import java.util.stream.Stream;
 
@@ -210,9 +212,10 @@
     @Override
     protected ScaleAndTranslation getOverviewScaleAndTranslationForNormalState() {
         if (SysUINavigationMode.getMode(this) == Mode.NO_BUTTON) {
-            float offscreenTranslationX = getDeviceProfile().widthPx
-                    - getOverviewPanel().getPaddingStart();
-            return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
+            PagedOrientationHandler layoutVertical =
+                ((RecentsView)getOverviewPanel()).getPagedViewOrientedState().getOrientationHandler();
+            return layoutVertical.getScaleAndTranslation(getDeviceProfile(),
+                getOverviewPanel());
         }
         return super.getOverviewScaleAndTranslationForNormalState();
     }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index fd55e07..64e053f 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -148,13 +149,18 @@
             // No-op
         }
 
-        static RectF getDefaultWindowTargetRect(DeviceProfile dp) {
+        static RectF getDefaultWindowTargetRect(PagedOrientationHandler orientationHandler,
+            DeviceProfile dp) {
             final int halfIconSize = dp.iconSizePx / 2;
-            final float targetCenterX = dp.availableWidthPx / 2f;
-            final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx;
+            float primaryDimension = orientationHandler
+                .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
+            float secondaryDimension = orientationHandler
+                .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
+            final float targetX =  primaryDimension / 2f;
+            final float targetY = secondaryDimension - dp.hotseatBarSizePx;
             // Fallback to animate to center of screen.
-            return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize,
-                    targetCenterX + halfIconSize, targetCenterY + halfIconSize);
+            return new RectF(targetX - halfIconSize, targetY - halfIconSize,
+                    targetX + halfIconSize, targetY + halfIconSize);
         }
 
     }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index ae0886b..501c6f0 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -19,10 +19,12 @@
 
 import android.app.ActivityManager;
 import android.content.Intent;
+
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+
+import java.io.PrintWriter;
 import java.util.ArrayList;
 
 /**
@@ -281,4 +283,13 @@
         mStateCallback.setState(STATE_RECENTS_ANIMATION_FINISHED);
         mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println("GestureState:");
+        pw.println("  gestureID=" + mGestureId);
+        pw.println("  runningTask=" + mRunningTask);
+        pw.println("  endTarget=" + mEndTarget);
+        pw.println("  finishingRecentsAnimationTaskId=" + mFinishingRecentsAnimationTaskId);
+        pw.println("  isRecentsAnimationRunning=" + isRecentsAnimationRunning());
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
new file mode 100644
index 0000000..832baf5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.RectF;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.Surface;
+
+import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.util.DefaultDisplay;
+
+import java.io.PrintWriter;
+
+/**
+ * Maintains state for supporting nav bars and tracking their gestures in multiple orientations.
+ * See {@link OrientationRectF#applyTransform(MotionEvent, boolean)} for transformation of
+ * MotionEvents from one orientation's coordinate space to another's.
+ *
+ * This class only supports single touch/pointer gesture tracking for touches started in a supported
+ * nav bar region.
+ */
+class OrientationTouchTransformer {
+
+    private static final String TAG = "OrientationTouchTransformer";
+    private static final boolean DEBUG = true;
+    private static final int MAX_ORIENTATIONS = 4;
+
+    private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
+    private final RectF mAssistantLeftRegion = new RectF();
+    private final RectF mAssistantRightRegion = new RectF();
+    private int mCurrentRotation;
+    private boolean mEnableMultipleRegions;
+    private Resources mResources;
+    private OrientationRectF mLastRectTouched;
+    private SysUINavigationMode.Mode mMode;
+    private QuickStepContractInfo mContractInfo;
+    private int mQuickStepStartingRotation = -1;
+
+    /** For testability */
+    interface QuickStepContractInfo {
+        float getWindowCornerRadius();
+    }
+
+    OrientationTouchTransformer(Resources resources, SysUINavigationMode.Mode mode,
+            QuickStepContractInfo contractInfo) {
+        mResources = resources;
+        mMode = mode;
+        mContractInfo = contractInfo;
+    }
+
+    void setNavigationMode(SysUINavigationMode.Mode newMode) {
+        this.mMode = newMode;
+    }
+
+    /**
+     * Sets the current nav bar region to listen to events for as determined by
+     * {@param info}. If multiple nav bar regions are enabled, then this region will be added
+     * alongside other regions.
+     * Ok to call multiple times
+     *
+     * @see #enableMultipleRegions(boolean, DefaultDisplay.Info)
+     */
+    void createOrAddTouchRegion(DefaultDisplay.Info info) {
+        mCurrentRotation = info.rotation;
+        if (mQuickStepStartingRotation > -1 && mCurrentRotation == mQuickStepStartingRotation) {
+            // Ignore nav bars in other rotations except for the one we started out in
+            resetSwipeRegions(info);
+            return;
+        }
+
+        OrientationRectF region = mSwipeTouchRegions.get(mCurrentRotation);
+        if (region != null) {
+            return;
+        }
+
+        if (mEnableMultipleRegions) {
+            mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(info));
+        } else {
+            resetSwipeRegions(info);
+        }
+    }
+
+    /**
+     * Call when we want to start tracking nav bar touch regions in multiple orientations.
+     * ALSO, you BETTER call this with {@param enableMultipleRegions} set to false once you're done.
+     *
+     * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
+     * @param info The current displayInfo
+     */
+    void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
+        mEnableMultipleRegions = enableMultipleRegions;
+        if (!enableMultipleRegions) {
+            mQuickStepStartingRotation = -1;
+            resetSwipeRegions(info);
+        } else {
+            if (mQuickStepStartingRotation < 0) {
+                mQuickStepStartingRotation = mLastRectTouched.mRotation;
+            }
+        }
+    }
+
+    /**
+     * Only saves the swipe region represented by {@param region}, clears the
+     * rest from {@link #mSwipeTouchRegions}
+     * To be called whenever we want to stop tracking more than one swipe region.
+     * Ok to call multiple times.
+     */
+    private void resetSwipeRegions(DefaultDisplay.Info region) {
+        if (DEBUG) {
+            Log.d(TAG, "clearing all regions except rotation: " + mCurrentRotation);
+        }
+
+        mCurrentRotation = region.rotation;
+        OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentRotation);
+        mSwipeTouchRegions.clear();
+        mSwipeTouchRegions.put(mCurrentRotation,
+                regionToKeep != null ? regionToKeep : createRegionForDisplay(region));
+    }
+
+    private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
+        if (DEBUG) {
+            Log.d(TAG, "creating rotation region for: " + mCurrentRotation);
+        }
+
+        Point size = display.realSize;
+        int rotation = display.rotation;
+        OrientationRectF orientationRectF =
+                new OrientationRectF(0, 0, size.x, size.y, rotation);
+        if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
+            int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+            orientationRectF.top = orientationRectF.bottom - touchHeight;
+
+            final int assistantWidth = mResources
+                    .getDimensionPixelSize(R.dimen.gestures_assistant_width);
+            final float assistantHeight = Math.max(touchHeight,
+                    mContractInfo.getWindowCornerRadius());
+            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = orientationRectF.bottom;
+            mAssistantLeftRegion.top = mAssistantRightRegion.top =
+                    orientationRectF.bottom - assistantHeight;
+
+            mAssistantLeftRegion.left = 0;
+            mAssistantLeftRegion.right = assistantWidth;
+
+            mAssistantRightRegion.right = orientationRectF.right;
+            mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
+        } else {
+            mAssistantLeftRegion.setEmpty();
+            mAssistantRightRegion.setEmpty();
+            switch (rotation) {
+                case Surface.ROTATION_90:
+                    orientationRectF.left = orientationRectF.right
+                            - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+                    break;
+                case Surface.ROTATION_270:
+                    orientationRectF.right = orientationRectF.left
+                            + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
+                    break;
+                default:
+                    orientationRectF.top = orientationRectF.bottom
+                            - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+            }
+        }
+
+        return orientationRectF;
+    }
+
+    boolean touchInAssistantRegion(MotionEvent ev) {
+        return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
+                || mAssistantRightRegion.contains(ev.getX(), ev.getY());
+
+    }
+
+    private int getNavbarSize(String resName) {
+        return ResourceUtils.getNavbarSize(resName, mResources);
+    }
+
+    boolean touchInValidSwipeRegions(float x, float y) {
+        if (mLastRectTouched != null) {
+            return mLastRectTouched.contains(x, y);
+        }
+        return false;
+    }
+
+    int getCurrentActiveRotation() {
+        if (mLastRectTouched == null) {
+            return 0;
+        } else {
+            return mLastRectTouched.mRotation;
+        }
+    }
+
+    public void transform(MotionEvent event) {
+        int eventAction = event.getActionMasked();
+        switch (eventAction) {
+            case ACTION_MOVE: {
+                if (mLastRectTouched == null) {
+                    return;
+                }
+                mLastRectTouched.applyTransform(event, true);
+                break;
+            }
+            case ACTION_CANCEL:
+            case ACTION_UP: {
+                if (mLastRectTouched == null) {
+                    return;
+                }
+                mLastRectTouched.applyTransform(event, true);
+                mLastRectTouched = null;
+                break;
+            }
+            case ACTION_POINTER_DOWN:
+            case ACTION_DOWN: {
+                if (mLastRectTouched != null) {
+                    return;
+                }
+
+                for (int i = 0; i < MAX_ORIENTATIONS; i++) {
+                    OrientationRectF rect = mSwipeTouchRegions.get(i);
+                    if (rect == null) {
+                        continue;
+                    }
+                    if (rect.applyTransform(event, false)) {
+                        mLastRectTouched = rect;
+                        if (DEBUG) {
+                            Log.d(TAG, "set active region: " + rect);
+                        }
+                        return;
+                    }
+                }
+                break;
+            }
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("OrientationTouchTransformerState: ");
+        pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
+        pw.println("  lastTouchedRegion=" + mLastRectTouched);
+        pw.println("  multipleRegionsEnabled=" + mEnableMultipleRegions);
+        StringBuilder regions = new StringBuilder("  currentTouchableRotations=");
+        for(int i = 0; i < mSwipeTouchRegions.size(); i++) {
+            OrientationRectF rectF = mSwipeTouchRegions.get(mSwipeTouchRegions.keyAt(i));
+            regions.append(rectF.mRotation).append(" ");
+        }
+        pw.println(regions.toString());
+    }
+
+    private class OrientationRectF extends RectF {
+
+        /**
+         * Delta to subtract width and height by because if we report the translated touch
+         * bounds as the width and height, calling {@link RectF#contains(float, float)} will
+         * be false
+         */
+        private float maxDelta = 0.001f;
+
+        private int mRotation;
+        private float mHeight;
+        private float mWidth;
+
+        OrientationRectF(float left, float top, float right, float bottom, int rotation) {
+            super(left, top, right, bottom);
+            this.mRotation = rotation;
+            mHeight = bottom - maxDelta;
+            mWidth = right - maxDelta;
+        }
+
+        @Override
+        public String toString() {
+            String s = super.toString();
+            s += " rotation: " + mRotation;
+            return s;
+        }
+
+        boolean applyTransform(MotionEvent event, boolean forceTransform) {
+            // TODO(b/149658423): See if we can use RotationHelper.getRotationMatrix here
+            MotionEvent tmp = MotionEvent.obtain(event);
+            Matrix outMatrix = new Matrix();
+            int delta = RotationHelper.deltaRotation(mCurrentRotation, mRotation);
+            switch (delta) {
+                case Surface.ROTATION_0:
+                    outMatrix.reset();
+                    break;
+                case Surface.ROTATION_90:
+                    outMatrix.setRotate(270);
+                    outMatrix.postTranslate(0, mHeight);
+                    break;
+                case Surface.ROTATION_180:
+                    outMatrix.setRotate(180);
+                    outMatrix.postTranslate(mHeight, mWidth);
+                    break;
+                case Surface.ROTATION_270:
+                    outMatrix.setRotate(90);
+                    outMatrix.postTranslate(mWidth, 0);
+                    break;
+            }
+
+            tmp.transform(outMatrix);
+            if (DEBUG) {
+                Log.d(TAG, "original: " + event.getX() + ", " + event.getY()
+                                + " new: " + tmp.getX() + ", " + tmp.getY()
+                                + " rect: " + this + " forceTransform: " + forceTransform
+                                + " contains: " + contains(tmp.getX(), tmp.getY()));
+            }
+
+            if (forceTransform || contains(tmp.getX(), tmp.getY())) {
+                event.transform(outMatrix);
+                tmp.recycle();
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 21a4918..8dd4aa4 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -39,7 +39,7 @@
 import java.util.function.Supplier;
 
 /**
- * Wrapper around RecentsAnimationController to help with some synchronization
+ * Wrapper around RecentsAnimationControllerCompat to help with some synchronization
  */
 public class RecentsAnimationController {
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index abe1592..d845650 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,8 +17,6 @@
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
-import static com.android.launcher3.ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE;
-import static com.android.launcher3.ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE;
 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;
@@ -40,20 +38,17 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Process;
 import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.MotionEvent;
-import android.view.Surface;
 
 import androidx.annotation.BinderThread;
 
+import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
@@ -63,6 +58,7 @@
 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;
@@ -80,6 +76,7 @@
     private final SysUINavigationMode mSysUiNavMode;
     private final DefaultDisplay mDefaultDisplay;
     private final int mDisplayId;
+    private int mDisplayRotation;
 
     private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
 
@@ -87,10 +84,7 @@
     private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
     private NavBarPosition mNavBarPosition;
 
-    private final RectF mSwipeUpTouchRegion = new RectF();
     private final Region mDeferredGestureRegion = new Region();
-    private final RectF mAssistantLeftRegion = new RectF();
-    private final RectF mAssistantRightRegion = new RectF();
     private boolean mAssistantAvailable;
     private float mAssistantVisibility;
 
@@ -106,10 +100,13 @@
         }
     };
 
+    private OrientationTouchTransformer mOrientationTouchTransformer;
+
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
     private final List<ComponentName> mGestureBlockedActivities;
+    private TaskStackChangeListener mFrozenTaskListener;
 
     public RecentsAnimationDeviceState(Context context) {
         final ContentResolver resolver = context.getContentResolver();
@@ -139,6 +136,8 @@
         };
         runOnDestroy(mExclusionListener::unregister);
 
+        setupOrientationSwipeHandler(context);
+
         // Register for navigation mode changes
         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
@@ -160,6 +159,26 @@
         }
     }
 
+    private void setupOrientationSwipeHandler(Context context) {
+        final Resources resources = context.getResources();
+        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+                () -> QuickStepContract.getWindowCornerRadius(resources));
+
+        if (!PagedView.sFlagForcedRotation) {
+            return;
+        }
+
+        mFrozenTaskListener = new TaskStackChangeListener() {
+            @Override
+            public void onRecentTaskListFrozenChanged(boolean frozen) {
+                mOrientationTouchTransformer.enableMultipleRegions(frozen, mDefaultDisplay.getInfo());
+            }
+        };
+        ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
+        runOnDestroy(() -> ActivityManagerWrapper.getInstance()
+                .unregisterTaskStackListener(mFrozenTaskListener));
+    }
+
     private void runOnDestroy(Runnable action) {
         mOnDestroyActions.add(action);
     }
@@ -198,7 +217,10 @@
             mExclusionListener.unregister();
         }
         mMode = newMode;
+
         mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo());
+
+        mOrientationTouchTransformer.setNavigationMode(mMode);
     }
 
     @Override
@@ -207,8 +229,10 @@
             return;
         }
 
+        mDisplayRotation = info.rotation;
         mNavBarPosition = new NavBarPosition(mMode, info);
         updateGestureTouchRegions();
+        mOrientationTouchTransformer.createOrAddTouchRegion(info);
     }
 
     /**
@@ -380,50 +404,14 @@
             return;
         }
 
-        Resources res = mContext.getResources();
-        DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
-        Point realSize = new Point(displayInfo.realSize);
-        mSwipeUpTouchRegion.set(0, 0, realSize.x, realSize.y);
-        if (mMode == NO_BUTTON) {
-            int touchHeight = ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res);
-            mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom - touchHeight;
-
-            final int assistantWidth = res.getDimensionPixelSize(R.dimen.gestures_assistant_width);
-            final float assistantHeight = Math.max(touchHeight,
-                    QuickStepContract.getWindowCornerRadius(res));
-            mAssistantLeftRegion.bottom = mAssistantRightRegion.bottom = mSwipeUpTouchRegion.bottom;
-            mAssistantLeftRegion.top = mAssistantRightRegion.top =
-                    mSwipeUpTouchRegion.bottom - assistantHeight;
-
-            mAssistantLeftRegion.left = 0;
-            mAssistantLeftRegion.right = assistantWidth;
-
-            mAssistantRightRegion.right = mSwipeUpTouchRegion.right;
-            mAssistantRightRegion.left = mSwipeUpTouchRegion.right - assistantWidth;
-        } else {
-            mAssistantLeftRegion.setEmpty();
-            mAssistantRightRegion.setEmpty();
-            switch (displayInfo.rotation) {
-                case Surface.ROTATION_90:
-                    mSwipeUpTouchRegion.left = mSwipeUpTouchRegion.right
-                            - ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res);
-                    break;
-                case Surface.ROTATION_270:
-                    mSwipeUpTouchRegion.right = mSwipeUpTouchRegion.left
-                            + ResourceUtils.getNavbarSize(NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE, res);
-                    break;
-                default:
-                    mSwipeUpTouchRegion.top = mSwipeUpTouchRegion.bottom
-                            - ResourceUtils.getNavbarSize(NAVBAR_BOTTOM_GESTURE_SIZE, res);
-            }
-        }
+        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 mSwipeUpTouchRegion.contains(event.getX(), event.getY());
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
     }
 
     /**
@@ -431,7 +419,8 @@
      *         is in the swipe up gesture region.
      */
     public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
-        return mSwipeUpTouchRegion.contains(event.getX(pointerIndex), event.getY(pointerIndex));
+        return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
+                event.getY(pointerIndex));
     }
 
     /**
@@ -490,11 +479,34 @@
     public boolean canTriggerAssistantAction(MotionEvent ev) {
         return mAssistantAvailable
                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
-                && (mAssistantLeftRegion.contains(ev.getX(), ev.getY())
-                        || mAssistantRightRegion.contains(ev.getX(), ev.getY()))
+                && mOrientationTouchTransformer.touchInAssistantRegion(ev)
                 && !isLockToAppActive();
     }
 
+    /**
+     * *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
+     */
+    public 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);
+    }
+
+    public void enableMultipleRegions(boolean enable) {
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+    }
+
+    public int getCurrentActiveRotation() {
+        return mOrientationTouchTransformer.getCurrentActiveRotation();
+    }
+
+    public int getDisplayRotation() {
+        return mDisplayRotation;
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("DeviceState:");
         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
@@ -504,5 +516,8 @@
         pw.println("  assistantAvailable=" + mAssistantAvailable);
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
+        pw.println("  currentActiveRotation=" + getCurrentActiveRotation());
+        pw.println("  displayRotation=" + getDisplayRotation());
+        mOrientationTouchTransformer.dump(pw);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index eb60601..0210a81 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -333,4 +333,15 @@
             }
         }
     }
+
+    public void onQuickSwitchToNewTask() {
+        //TODO(b/150250451) add back in after big CL goes through
+//        if (mSystemUiProxy != null) {
+//            try {
+//                mSystemUiProxy.onQuickSwitchToNewTask();
+//            } catch (RemoteException e) {
+//                Log.w(TAG, "Failed call onQuickstepStarted", e);
+//            }
+//        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 1d8a79f..ba99016 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -33,6 +33,9 @@
 
 import java.lang.annotation.Retention;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 public class LayoutUtils {
 
     private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
diff --git a/res/values/config.xml b/res/values/config.xml
index 8aff6da..35e5e6d 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -121,5 +121,41 @@
     <bool name="config_enableTaskSnapshotPreloading">true</bool>
 
     <!-- Configuration resources -->
-    <array name="dynamic_resources"> </array>
+    <item name="all_apps_spring_damping_ratio" type="dimen" format="float">0.75</item>
+    <item name="all_apps_spring_stiffness" type="dimen" format="float">600</item>
+
+    <item name="dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.5</item>
+    <item name="dismiss_task_trans_y_stiffness" type="dimen" format="float">1500</item>
+
+    <item name="dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.5</item>
+    <item name="dismiss_task_trans_x_stiffness" type="dimen" format="float">1500</item>
+
+    <item name="horizontal_spring_damping_ratio" type="dimen" format="float">0.75</item>
+    <item name="horizontal_spring_stiffness" type="dimen" format="float">200</item>
+
+    <item name="swipe_up_rect_damping_ratio" type="dimen" format="float">0.75</item>
+    <item name="swipe_up_rect_stiffness" type="dimen" format="float">200</item>
+
+    <item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
+    <item name="staggered_stiffness" type="dimen" format="float">150</item>
+
+    <array name="dynamic_resources">
+        <item>@dimen/all_apps_spring_damping_ratio</item>
+        <item>@dimen/all_apps_spring_stiffness</item>
+
+        <item>@dimen/dismiss_task_trans_y_damping_ratio</item>
+        <item>@dimen/dismiss_task_trans_y_stiffness</item>
+
+        <item>@dimen/dismiss_task_trans_x_damping_ratio</item>
+        <item>@dimen/dismiss_task_trans_x_stiffness</item>
+
+        <item>@dimen/horizontal_spring_damping_ratio</item>
+        <item>@dimen/horizontal_spring_stiffness</item>
+
+        <item>@dimen/swipe_up_rect_damping_ratio</item>
+        <item>@dimen/swipe_up_rect_stiffness</item>
+
+        <item>@dimen/staggered_damping_ratio</item>
+        <item>@dimen/staggered_stiffness</item>
+    </array>
 </resources>
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 01893e9..ef6bd3d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -16,11 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
-import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
-import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
-
 import android.animation.LayoutTransition;
 import android.animation.TimeInterpolator;
 import android.annotation.SuppressLint;
@@ -35,6 +30,7 @@
 import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -49,13 +45,25 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.PagedViewOrientedState;
 import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.Thunk;
 
 import java.util.ArrayList;
 
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
+import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.touch.OverScroll.OVERSCROLL_DAMP_FACTOR;
+import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE;
+import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
+import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
+
 /**
  * An abstraction of the original Workspace which supports browsing through a
  * sequential list of "pages"
@@ -64,6 +72,8 @@
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
 
+    public static boolean sFlagForcedRotation = false;
+
     public static final int INVALID_PAGE = -1;
     protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
@@ -97,8 +107,8 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     protected int mNextPage = INVALID_PAGE;
-    protected int mMinScrollX;
-    protected int mMaxScrollX;
+    protected int mMaxScroll;
+    protected int mMinScroll;
     protected OverScroller mScroller;
     private Interpolator mDefaultInterpolator;
     private VelocityTracker mVelocityTracker;
@@ -106,9 +116,12 @@
 
     private float mDownMotionX;
     private float mDownMotionY;
-    private float mLastMotionX;
-    private float mLastMotionXRemainder;
-    private float mTotalMotionX;
+    private float mDownMotionPrimary;
+    private float mLastMotion;
+    private float mLastMotionRemainder;
+    private float mTotalMotion;
+    protected PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
+    protected final PagedViewOrientedState mOrientationState = new PagedViewOrientedState();
 
     protected int[] mPageScrolls;
     private boolean mIsBeingDragged;
@@ -123,11 +136,14 @@
 
     protected boolean mIsPageInTransition = false;
 
-    protected float mSpringOverScrollX;
+    protected float mSpringOverScroll;
 
     protected boolean mWasInOverscroll = false;
 
-    protected int mUnboundedScrollX;
+    protected int mUnboundedScroll;
+
+    protected int mLayoutRotation = Surface.ROTATION_0;
+    protected int mDisplayRotation = Surface.ROTATION_0;
 
     // Page Indicator
     @Thunk int mPageIndicatorViewId;
@@ -166,11 +182,12 @@
      * Initializes various states for this workspace.
      */
     protected void init() {
-        mScroller = new OverScroller(getContext());
+        Context context = getContext();
+        mScroller = new OverScroller(context);
         setDefaultInterpolator(Interpolators.SCROLL);
         mCurrentPage = 0;
 
-        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+        final ViewConfiguration configuration = ViewConfiguration.get(context);
         mTouchSlop = configuration.getScaledPagingTouchSlop();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
 
@@ -182,6 +199,8 @@
         if (Utilities.ATLEAST_OREO) {
             setDefaultFocusHighlightEnabled(false);
         }
+
+        sFlagForcedRotation = Utilities.isForcedRotation(context);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -236,12 +255,12 @@
      */
     protected void updateCurrentPageScroll() {
         // If the current page is invalid, just reset the scroll position to zero
-        int newX = 0;
+        int newPosition = 0;
         if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
-            newX = getScrollForPage(mCurrentPage);
+            newPosition = getScrollForPage(mCurrentPage);
         }
-        scrollTo(newX, 0);
-        mScroller.startScroll(mScroller.getCurrPos(), newX - mScroller.getCurrPos());
+        mOrientationHandler.set(this, VIEW_SCROLL_TO, newPosition);
+        mOrientationHandler.scrollerStartScroll(mScroller, newPosition);
         forceFinishScroller(true);
     }
 
@@ -285,7 +304,7 @@
         int dir = !mIsRtl ? 1 : - 1;
         int currScroll = getScrollForPage(page);
         int prevScroll;
-        while (currScroll < mMinScrollX) {
+        while (currScroll < mMinScroll) {
             page += dir;
             prevScroll = currScroll;
             currScroll = getScrollForPage(page);
@@ -294,7 +313,7 @@
                 break;
             }
         }
-        while (currScroll > mMaxScrollX) {
+        while (currScroll > mMaxScroll) {
             page -= dir;
             prevScroll = currScroll;
             currScroll = getScrollForPage(page);
@@ -378,45 +397,73 @@
                 AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
     }
 
-    protected int getUnboundedScrollX() {
-        return mUnboundedScrollX;
+    protected int getUnboundedScroll() {
+        return mUnboundedScroll;
+    }
+
+    protected void updateLayoutRotation(int touchRotation) {
+        setLayoutRotation(touchRotation, mDisplayRotation);
+    }
+
+    /** @param touchRotation Must be one of {@link android.view.Surface.ROTATION_0/90/180/270} */
+    public void setLayoutRotation(int touchRotation, int displayRotation) {
+        if (mLayoutRotation == touchRotation && mDisplayRotation == displayRotation) {
+            return;
+        }
+
+        mOrientationState.update(touchRotation, displayRotation);
+        mOrientationHandler = mOrientationState.getOrientationHandler();
+        mLayoutRotation = touchRotation;
+        mDisplayRotation = displayRotation;
+        requestLayout();
+    }
+
+    public PagedViewOrientedState getPagedViewOrientedState() {
+        return mOrientationState;
+    }
+
+    public PagedOrientationHandler getPagedOrientationHandler() {
+        return getPagedViewOrientedState().getOrientationHandler();
+    }
+
+    public void disableMultipleLayoutRotations(boolean disable) {
+        mOrientationState.disableMultipleOrientations(disable);
+        mOrientationHandler = mOrientationState.getOrientationHandler();
+        requestLayout();
     }
 
     @Override
     public void scrollBy(int x, int y) {
-        scrollTo(getUnboundedScrollX() + x, getScrollY() + y);
+        mOrientationHandler.delegateScrollBy(this, getUnboundedScroll(), x, y);
     }
 
     @Override
     public void scrollTo(int x, int y) {
-        mUnboundedScrollX = x;
+        int primaryScroll = mOrientationHandler.getPrimaryValue(x, y);
+        int secondaryScroll = mOrientationHandler.getSecondaryValue(x, y);
+        mUnboundedScroll = primaryScroll;
 
-        boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < mMinScrollX);
-        boolean isXAfterLastPage = mIsRtl ? (x < mMinScrollX) : (x > mMaxScrollX);
-
-        if (!isXBeforeFirstPage && !isXAfterLastPage) {
-            mSpringOverScrollX = 0;
+        boolean isBeforeFirstPage = mIsRtl ?
+            (primaryScroll > mMaxScroll) : (primaryScroll < mMinScroll);
+        boolean isAfterLastPage = mIsRtl ?
+            (primaryScroll < mMinScroll) : (primaryScroll > mMaxScroll);
+        if (!isBeforeFirstPage && !isAfterLastPage) {
+            mSpringOverScroll = 0;
         }
 
-        if (isXBeforeFirstPage) {
-            super.scrollTo(mIsRtl ? mMaxScrollX : mMinScrollX, y);
+        if (isBeforeFirstPage) {
+            mOrientationHandler.delegateScrollTo(this,
+                secondaryScroll, mIsRtl ? mMaxScroll : mMinScroll);
             if (mAllowOverScroll) {
                 mWasInOverscroll = true;
-                if (mIsRtl) {
-                    overScroll(x - mMaxScrollX);
-                } else {
-                    overScroll(x - mMinScrollX);
-                }
+                overScroll(primaryScroll - (mIsRtl ? mMaxScroll : mMinScroll));
             }
-        } else if (isXAfterLastPage) {
-            super.scrollTo(mIsRtl ? mMinScrollX : mMaxScrollX, y);
+        } else if (isAfterLastPage) {
+            mOrientationHandler.delegateScrollTo(this,
+                secondaryScroll, mIsRtl ? mMinScroll : mMaxScroll);
             if (mAllowOverScroll) {
                 mWasInOverscroll = true;
-                if (mIsRtl) {
-                    overScroll(x - mMinScrollX);
-                } else {
-                    overScroll(x - mMaxScrollX);
-                }
+                overScroll(primaryScroll - (mIsRtl ? mMinScroll : mMaxScroll));
             }
         } else {
             if (mWasInOverscroll) {
@@ -425,7 +472,13 @@
             }
             super.scrollTo(x, y);
         }
+    }
 
+    /**
+     * Helper for {@link PagedOrientationHandler} to be able to call parent's scrollTo method
+     */
+    public void superScrollTo(int x, int y) {
+        super.scrollTo(x, y);
     }
 
     private void sendScrollAccessibilityEvent() {
@@ -436,9 +489,7 @@
                 ev.setScrollable(true);
                 ev.setScrollX(getScrollX());
                 ev.setScrollY(getScrollY());
-                ev.setMaxScrollX(mMaxScrollX);
-                ev.setMaxScrollY(0);
-
+                mOrientationHandler.setMaxScroll(ev, mMaxScroll);
                 sendAccessibilityEventUnchecked(ev);
             }
         }
@@ -459,9 +510,10 @@
     protected boolean computeScrollHelper(boolean shouldInvalidate) {
         if (mScroller.computeScrollOffset()) {
             // Don't bother scrolling if the page does not need to be moved
-            if (getUnboundedScrollX() != mScroller.getCurrPos()
-                    || getScrollX() != mScroller.getCurrPos()) {
-                scrollTo(mScroller.getCurrPos(), 0);
+            int currentScroll = mOrientationHandler.getPrimaryScroll(this);
+            if (mUnboundedScroll != mScroller.getCurrPos()
+                || currentScroll != mScroller.getCurrPos()) {
+                mOrientationHandler.set(this, VIEW_SCROLL_TO, mScroller.getCurrPos());
             }
             if (shouldInvalidate) {
                 invalidate();
@@ -580,7 +632,8 @@
 
         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
 
-        if (getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC)) {
+        boolean isScrollChanged = getPageScrolls(mPageScrolls, true, SIMPLE_SCROLL_LOGIC);
+        if (isScrollChanged) {
             pageScrollChanged = true;
         }
 
@@ -621,7 +674,6 @@
     /**
      * Initializes {@code outPageScrolls} with scroll positions for view at that index. The length
      * of {@code outPageScrolls} should be same as the the childCount
-     *
      */
     protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren,
             ComputePageScrollsLogic scrollLogic) {
@@ -631,36 +683,30 @@
         final int endIndex = mIsRtl ? -1 : childCount;
         final int delta = mIsRtl ? -1 : 1;
 
-        final int verticalCenter = (getPaddingTop() + getMeasuredHeight() + mInsets.top
-                - mInsets.bottom - getPaddingBottom()) / 2;
+        final int pageCenter = mOrientationHandler.getCenterForPage(this, mInsets);
 
-        final int scrollOffsetLeft = mInsets.left + getPaddingLeft();
-        final int scrollOffsetRight = getWidth() - getPaddingRight() - mInsets.right;
+        final int scrollOffsetStart = mOrientationHandler.getScrollOffsetStart(this, mInsets);
+        final int scrollOffsetEnd = mOrientationHandler.getScrollOffsetEnd(this, mInsets);
         boolean pageScrollChanged = false;
 
-        for (int i = startIndex, childLeft = scrollOffsetLeft; i != endIndex; i += delta) {
+        for (int i = startIndex, childStart = scrollOffsetStart; i != endIndex; i += delta) {
             final View child = getPageAt(i);
             if (scrollLogic.shouldIncludeView(child)) {
-                final int childWidth = child.getMeasuredWidth();
-                final int childRight = childLeft + childWidth;
-
-                if (layoutChildren) {
-                    final int childHeight = child.getMeasuredHeight();
-                    final int childTop = verticalCenter - childHeight / 2;
-                    child.layout(childLeft, childTop, childRight, childTop + childHeight);
-                }
+                ChildBounds bounds = mOrientationHandler.getChildBounds(child, childStart,
+                    pageCenter, layoutChildren);
+                final int primaryDimension = bounds.primaryDimension;
+                final int childPrimaryEnd = bounds.childPrimaryEnd;
 
                 // In case the pages are of different width, align the page to left or right edge
                 // based on the orientation.
                 final int pageScroll = mIsRtl
-                        ? (childLeft - scrollOffsetLeft)
-                        : Math.max(0, childRight  - scrollOffsetRight);
+                    ? (childStart - scrollOffsetStart)
+                    : Math.max(0, childPrimaryEnd  - scrollOffsetEnd);
                 if (outPageScrolls[i] != pageScroll) {
                     pageScrollChanged = true;
                     outPageScrolls[i] = pageScroll;
                 }
-
-                childLeft += childWidth + mPageSpacing + getChildGap();
+                childStart += primaryDimension + mPageSpacing + getChildGap();
             }
         }
         return pageScrollChanged;
@@ -671,15 +717,15 @@
     }
 
     protected void updateMinAndMaxScrollX() {
-        mMinScrollX = computeMinScrollX();
-        mMaxScrollX = computeMaxScrollX();
+        mMinScroll = computeMinScroll();
+        mMaxScroll = computeMaxScroll();
     }
 
-    protected int computeMinScrollX() {
+    protected int computeMinScroll() {
         return 0;
     }
 
-    protected int computeMaxScrollX() {
+    protected int computeMaxScroll() {
         int childCount = getChildCount();
         if (childCount > 0) {
             final int index = mIsRtl ? 0 : childCount - 1;
@@ -722,7 +768,8 @@
 
     protected int getChildOffset(int index) {
         if (index < 0 || index > getChildCount() - 1) return 0;
-        return getPageAt(index).getLeft();
+        View pageAtIndex = getPageAt(index);
+        return mOrientationHandler.getChildStart(pageAtIndex);
     }
 
     @Override
@@ -873,13 +920,13 @@
             case MotionEvent.ACTION_MOVE: {
                 /*
                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
-                 * whether the user has moved far enough from his original down touch.
+                 * whether the user has moved far enough from their original down touch.
                  */
                 if (mActivePointerId != INVALID_POINTER) {
                     determineScrollingStart(ev);
                 }
                 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
-                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
+                // event. in that case, treat the first occurrence of a move event as a ACTION_DOWN
                 // i.e. fall through to the next case (don't break)
                 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
                 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
@@ -892,9 +939,9 @@
                 // Remember location of down touch
                 mDownMotionX = x;
                 mDownMotionY = y;
-                mLastMotionX = x;
-                mLastMotionXRemainder = 0;
-                mTotalMotionX = 0;
+                mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
+                mLastMotionRemainder = 0;
+                mTotalMotion = 0;
                 mActivePointerId = ev.getPointerId(0);
 
                 updateIsBeingDraggedOnTouchDown();
@@ -956,17 +1003,17 @@
         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
         if (pointerIndex == -1) return;
 
-        final float x = ev.getX(pointerIndex);
-        final int xDiff = (int) Math.abs(x - mLastMotionX);
+        final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev, pointerIndex);
+        final int diff = (int) Math.abs(primaryDirection - mLastMotion);
         final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
-        boolean xMoved = xDiff > touchSlop;
+        boolean moved = diff > touchSlop;
 
-        if (xMoved) {
+        if (moved) {
             // Scroll if the user moved far enough along the X axis
             mIsBeingDragged = true;
-            mTotalMotionX += Math.abs(mLastMotionX - x);
-            mLastMotionX = x;
-            mLastMotionXRemainder = 0;
+            mTotalMotion += Math.abs(mLastMotion - primaryDirection);
+            mLastMotion = primaryDirection;
+            mLastMotionRemainder = 0;
             onScrollInteractionBegin();
             pageBeginTransition();
             // Stop listening for things like pinches.
@@ -1033,10 +1080,9 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
-        if (mScroller.isSpringing() && mSpringOverScrollX != 0) {
+        if (mScroller.isSpringing() && mSpringOverScroll != 0) {
             int saveCount = canvas.save();
-
-            canvas.translate(-mSpringOverScrollX, 0);
+            mOrientationHandler.set(canvas, CANVAS_TRANSLATE, -mSpringOverScroll);
             super.dispatchDraw(canvas);
 
             canvas.restoreToCount(saveCount);
@@ -1046,25 +1092,27 @@
     }
 
     protected void dampedOverScroll(int amount) {
-        mSpringOverScrollX = amount;
+        mSpringOverScroll = amount;
         if (amount == 0) {
             return;
         }
 
-        int overScrollAmount = OverScroll.dampedScroll(amount, getMeasuredWidth());
-        mSpringOverScrollX = overScrollAmount;
+        int size = mOrientationHandler.getMeasuredSize(this);
+        int overScrollAmount = OverScroll.dampedScroll(amount, size);
+        mSpringOverScroll = overScrollAmount;
         if (mScroller.isSpringing()) {
             invalidate();
             return;
         }
 
-        int x = Utilities.boundToRange(getScrollX(), mMinScrollX, mMaxScrollX);
-        super.scrollTo(x + overScrollAmount, getScrollY());
+        int primaryScroll = mOrientationHandler.getPrimaryScroll(this);
+        int boundedScroll = Utilities.boundToRange(primaryScroll, mMinScroll, mMaxScroll);
+        mOrientationHandler.delegateScrollTo(this, boundedScroll + overScrollAmount);
         invalidate();
     }
 
     protected void overScroll(int amount) {
-        mSpringOverScrollX = amount;
+        mSpringOverScroll = amount;
         if (mScroller.isSpringing()) {
             invalidate();
             return;
@@ -1073,11 +1121,8 @@
         if (amount == 0) return;
 
         if (mFreeScroll && !mScroller.isFinished()) {
-            if (amount < 0) {
-                super.scrollTo(mMinScrollX + amount, getScrollY());
-            } else {
-                super.scrollTo(mMaxScrollX + amount, getScrollY());
-            }
+            int scrollAmount = amount < 0 ? mMinScroll + amount : mMaxScroll + amount;
+            mOrientationHandler.delegateScrollTo(this, scrollAmount);
         } else {
             dampedOverScroll(amount);
         }
@@ -1127,37 +1172,37 @@
             }
 
             // Remember where the motion event started
-            mDownMotionX = mLastMotionX = ev.getX();
+            mDownMotionX = ev.getX();
             mDownMotionY = ev.getY();
-            mLastMotionXRemainder = 0;
-            mTotalMotionX = 0;
+            mDownMotionPrimary = mLastMotion = mOrientationHandler.getPrimaryDirection(ev, 0);
+            mLastMotionRemainder = 0;
+            mTotalMotion = 0;
             mActivePointerId = ev.getPointerId(0);
-
             if (mIsBeingDragged) {
                 onScrollInteractionBegin();
                 pageBeginTransition();
             }
             break;
 
-        case MotionEvent.ACTION_MOVE:
-            if (mIsBeingDragged) {
+            case MotionEvent.ACTION_MOVE:
+                if (mIsBeingDragged) {
                 // Scroll to follow the motion event
                 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
 
                 if (pointerIndex == -1) return true;
 
-                final float x = ev.getX(pointerIndex);
-                final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
-
-                mTotalMotionX += Math.abs(deltaX);
+                float direction = mOrientationHandler.getPrimaryDirection(ev, pointerIndex);
+                float delta = mLastMotion + mLastMotionRemainder - direction;
+                mTotalMotion += Math.abs(delta);
 
                 // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
                 // keep the remainder because we are actually testing if we've moved from the last
                 // scrolled position (which is discrete).
-                if (Math.abs(deltaX) >= 1.0f) {
-                    scrollBy((int) deltaX, 0);
-                    mLastMotionX = x;
-                    mLastMotionXRemainder = deltaX - (int) deltaX;
+                if (Math.abs(delta) >= 1.0f) {
+                    mLastMotion = direction;
+                    mLastMotionRemainder = delta - (int) delta;
+
+                    mOrientationHandler.set(this, VIEW_SCROLL_BY, (int) delta);
                 } else {
                     awakenScrollBars();
                 }
@@ -1170,27 +1215,31 @@
             if (mIsBeingDragged) {
                 final int activePointerId = mActivePointerId;
                 final int pointerIndex = ev.findPointerIndex(activePointerId);
-                final float x = ev.getX(pointerIndex);
+                final float primaryDirection = mOrientationHandler.getPrimaryDirection(ev,
+                    pointerIndex);
                 final VelocityTracker velocityTracker = mVelocityTracker;
                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
-                int velocityX = (int) velocityTracker.getXVelocity(mActivePointerId);
-                final int deltaX = (int) (x - mDownMotionX);
-                final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
-                boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
-                        SIGNIFICANT_MOVE_THRESHOLD;
 
-                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
-                boolean isFling = mTotalMotionX > mTouchSlop && shouldFlingForVelocity(velocityX);
-                boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
-                boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
+                int velocity = (int) mOrientationHandler.getPrimaryVelocity(velocityTracker,
+                    mActivePointerId);
+                int delta = (int) (primaryDirection - mDownMotionPrimary);
+                int pageOrientedSize = mOrientationHandler.getMeasuredSize(getPageAt(mCurrentPage));
+
+                boolean isSignificantMove = Math.abs(delta) > pageOrientedSize *
+                    SIGNIFICANT_MOVE_THRESHOLD;
+
+                mTotalMotion += Math.abs(mLastMotion + mLastMotionRemainder - primaryDirection);
+                boolean isFling = mTotalMotion > mTouchSlop && shouldFlingForVelocity(velocity);
+                boolean isDeltaLeft = mIsRtl ? delta > 0 : delta < 0;
+                boolean isVelocityLeft = mIsRtl ? velocity > 0 : velocity < 0;
 
                 if (!mFreeScroll) {
                     // In the case that the page is moved far to one direction and then is flung
                     // in the opposite direction, we use a threshold to determine whether we should
                     // just return to the starting page, or if we should skip one further.
                     boolean returnToOriginalPage = false;
-                    if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
-                            Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
+                    if (Math.abs(delta) > pageOrientedSize * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
+                            Math.signum(velocity) != Math.signum(delta) && isFling) {
                         returnToOriginalPage = true;
                     }
 
@@ -1199,15 +1248,15 @@
                     // test for a large move if a fling has been registered. That is, a large
                     // move to the left and fling to the right will register as a fling to the right.
 
-                    if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
-                            (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
+                    if (((isSignificantMove && !isDeltaLeft && !isFling) ||
+                            (isFling && !isVelocityLeft)) && mCurrentPage > 0) {
                         finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
-                        snapToPageWithVelocity(finalPage, velocityX);
-                    } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
-                            (isFling && isVelocityXLeft)) &&
+                        snapToPageWithVelocity(finalPage, velocity);
+                    } else if (((isSignificantMove && isDeltaLeft && !isFling) ||
+                            (isFling && isVelocityLeft)) &&
                             mCurrentPage < getChildCount() - 1) {
                         finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
-                        snapToPageWithVelocity(finalPage, velocityX);
+                        snapToPageWithVelocity(finalPage, velocity);
                     } else {
                         snapToDestination();
                     }
@@ -1216,38 +1265,40 @@
                         abortScrollerAnimation(true);
                     }
 
-                    int initialScrollX = getScrollX();
+                    int initialScroll = mOrientationHandler.getPrimaryScroll(this);
+                    int maxScroll = mMaxScroll;
+                    int minScroll = mMinScroll;
 
-                    if (((initialScrollX >= mMaxScrollX) && (isVelocityXLeft || !isFling)) ||
-                            ((initialScrollX <= mMinScrollX) && (!isVelocityXLeft || !isFling))) {
-                        mScroller.springBack(getScrollX(), mMinScrollX, mMaxScrollX);
+                    if (((initialScroll >= maxScroll) && (isVelocityLeft || !isFling)) ||
+                        ((initialScroll <= minScroll) && (!isVelocityLeft || !isFling))) {
+                        mScroller.springBack(initialScroll, minScroll, maxScroll);
                         mNextPage = getPageNearestToCenterOfScreen();
                     } else {
                         mScroller.setInterpolator(mDefaultInterpolator);
-                        mScroller.fling(initialScrollX, -velocityX,
-                                mMinScrollX, mMaxScrollX,
-                                Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR));
+                        mScroller.fling(initialScroll, -velocity,
+                            minScroll, maxScroll,
+                            Math.round(getWidth() * 0.5f * OVERSCROLL_DAMP_FACTOR));
 
-                        int finalX = mScroller.getFinalPos();
-                        mNextPage = getPageNearestToCenterOfScreen(finalX);
+                        int finalPos = mScroller.getFinalPos();
+                        mNextPage = getPageNearestToCenterOfScreen(finalPos);
 
                         int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1);
                         int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0);
-                        if (finalX > mMinScrollX && finalX < mMaxScrollX) {
+                        if (finalPos > minScroll && finalPos < maxScroll) {
                             // If scrolling ends in the half of the added space that is closer to
                             // the end, settle to the end. Otherwise snap to the nearest page.
                             // If flinging past one of the ends, don't change the velocity as it
                             // will get stopped at the end anyway.
-                            int pageSnappedX = finalX < (firstPageScroll + mMinScrollX) / 2
-                                    ? mMinScrollX
-                                    : finalX > (lastPageScroll + mMaxScrollX) / 2
-                                            ? mMaxScrollX
-                                            : getScrollForPage(mNextPage);
+                            int pageSnapped = finalPos < (firstPageScroll + minScroll) / 2
+                                ? minScroll
+                                : finalPos > (lastPageScroll + maxScroll) / 2
+                                    ? maxScroll
+                                    : getScrollForPage(mNextPage);
 
-                            mScroller.setFinalPos(pageSnappedX);
+                            mScroller.setFinalPos(pageSnapped);
                             // Ensure the scroll/snap doesn't happen too fast;
                             int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION
-                                    - mScroller.getDuration();
+                                - mScroller.getDuration();
                             if (extraScrollDuration > 0) {
                                 mScroller.extendDuration(extraScrollDuration);
                             }
@@ -1279,8 +1330,8 @@
         return true;
     }
 
-    protected boolean shouldFlingForVelocity(int velocityX) {
-        return Math.abs(velocityX) > mFlingThresholdVelocity;
+    protected boolean shouldFlingForVelocity(int velocity) {
+        return Math.abs(velocity) > mFlingThresholdVelocity;
     }
 
     private void resetTouchState() {
@@ -1364,8 +1415,9 @@
             // active pointer and adjust accordingly.
             // TODO: Make this decision more intelligent.
             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
-            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
-            mLastMotionXRemainder = 0;
+            mLastMotion = mDownMotionPrimary = mOrientationHandler.getPrimaryDirection(ev,
+                newPointerIndex);
+            mLastMotionRemainder = 0;
             mActivePointerId = ev.getPointerId(newPointerIndex);
             if (mVelocityTracker != null) {
                 mVelocityTracker.clear();
@@ -1383,19 +1435,20 @@
     }
 
     public int getPageNearestToCenterOfScreen() {
-        return getPageNearestToCenterOfScreen(getScrollX());
+        return getPageNearestToCenterOfScreen(mOrientationHandler.getPrimaryScroll(this));
     }
 
-    private int getPageNearestToCenterOfScreen(int scaledScrollX) {
-        int screenCenter = scaledScrollX + (getMeasuredWidth() / 2);
+    private int getPageNearestToCenterOfScreen(int scaledScroll) {
+        int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
+        int screenCenter = scaledScroll + (pageOrientationSize / 2);
         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
         int minDistanceFromScreenCenterIndex = -1;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; ++i) {
             View layout = getPageAt(i);
-            int childWidth = layout.getMeasuredWidth();
-            int halfChildWidth = (childWidth / 2);
-            int childCenter = getChildOffset(i) + halfChildWidth;
+            int childSize = mOrientationHandler.getMeasuredSize(layout);
+            int halfChildSize = (childSize / 2);
+            int childCenter = getChildOffset(i) + halfChildSize;
             int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
                 minDistanceFromScreenCenter = distanceFromScreenCenter;
@@ -1410,7 +1463,8 @@
     }
 
     protected boolean isInOverScroll() {
-        return (getScrollX() > mMaxScrollX || getScrollX() < mMinScrollX);
+        int scroll = mOrientationHandler.getPrimaryScroll(this);
+        return scroll > mMaxScroll || scroll < mMinScroll;
     }
 
     protected int getPageSnapDuration() {
@@ -1432,10 +1486,10 @@
 
     protected boolean snapToPageWithVelocity(int whichPage, int velocity) {
         whichPage = validateNewPage(whichPage);
-        int halfScreenSize = getMeasuredWidth() / 2;
+        int halfScreenSize = mOrientationHandler.getMeasuredSize(this) / 2;
 
-        final int newX = getScrollForPage(whichPage);
-        int delta = newX - getUnboundedScrollX();
+        final int newLoc = getScrollForPage(whichPage);
+        int delta = newLoc - getUnboundedScroll();
         int duration = 0;
 
         if (Math.abs(velocity) < mMinFlingVelocity) {
@@ -1462,7 +1516,7 @@
 
         if (QUICKSTEP_SPRINGS.get()) {
             return snapToPage(whichPage, delta, duration, false, null,
-                    velocity * Math.signum(newX - getUnboundedScrollX()), true);
+                    velocity * Math.signum(delta), true);
         } else {
             return snapToPage(whichPage, delta, duration);
         }
@@ -1488,8 +1542,8 @@
             TimeInterpolator interpolator) {
         whichPage = validateNewPage(whichPage);
 
-        int newX = getScrollForPage(whichPage);
-        final int delta = newX - getUnboundedScrollX();
+        int newLoc = getScrollForPage(whichPage);
+        final int delta = newLoc - getUnboundedScroll();
         return snapToPage(whichPage, delta, duration, immediate, interpolator, 0, false);
     }
 
@@ -1535,9 +1589,9 @@
         }
 
         if (spring && QUICKSTEP_SPRINGS.get()) {
-            mScroller.startScrollSpring(getUnboundedScrollX(), delta, duration, velocity);
+            mScroller.startScrollSpring(getUnboundedScroll(), delta, duration, velocity);
         } else {
-            mScroller.startScroll(getUnboundedScrollX(), delta, duration);
+            mScroller.startScroll(getUnboundedScroll(), delta, duration);
         }
 
         updatePageIndicator();
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 7f327a5..403d779 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -21,12 +21,12 @@
 import android.util.TypedValue;
 
 public class ResourceUtils {
+    public static final int DEFAULT_NAVBAR_VALUE = 48;
     public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
     public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
 
-
     public static int getNavbarSize(String resName, Resources res) {
-        return getDimenByName(resName, res, 48);
+        return getDimenByName(resName, res, DEFAULT_NAVBAR_VALUE);
     }
 
     public static int getDimenByName(String resName, Resources res, int defaultValue) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e0e4cc0..9780630 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME;
 
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -127,6 +128,11 @@
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
     }
 
+    public static boolean isForcedRotation(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+            FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0;
+    }
+
     // An intent extra to indicate the horizontal scroll of the wallpaper.
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a8f492f..590c620 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -966,8 +966,8 @@
 
     private boolean isScrollingOverlay() {
         return mLauncherOverlay != null &&
-                ((mIsRtl && getUnboundedScrollX() > mMaxScrollX)
-                        || (!mIsRtl && getUnboundedScrollX() < mMinScrollX));
+                ((mIsRtl && getUnboundedScroll() > mMaxScroll)
+                        || (!mIsRtl && getUnboundedScroll() < mMinScroll));
     }
 
     @Override
@@ -1003,7 +1003,7 @@
 
     public void showPageIndicatorAtCurrentScroll() {
         if (mPageIndicator != null) {
-            mPageIndicator.setScroll(getScrollX(), computeMaxScrollX());
+            mPageIndicator.setScroll(getScrollX(), computeMaxScroll());
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index 5b73940..ab4cb6b 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -48,7 +48,7 @@
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
-        mPageIndicator.setScroll(l, mMaxScrollX);
+        mPageIndicator.setScroll(l, mMaxScroll);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index e49ff30..2179162 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -32,8 +32,10 @@
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.anim.SpringObjectAnimator;
+import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
+import com.android.systemui.plugins.ResourceProvider;
 
 /**
  * Handles AllApps view transition.
@@ -47,9 +49,6 @@
  */
 public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
 
-    private static final float SPRING_DAMPING_RATIO = 0.75f;
-    private static final float SPRING_STIFFNESS = 600f;
-
     public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
             new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
 
@@ -187,8 +186,12 @@
 
     public Animator createSpringAnimation(float... progressValues) {
         if (UNSTABLE_SPRINGS.get()) {
+            ResourceProvider rp = DynamicResource.provider(mLauncher);
+            float damping = rp.getFloat(R.dimen.all_apps_spring_damping_ratio);
+            float stiffness = rp.getFloat(R.dimen.all_apps_spring_stiffness);
+
             return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
-                    SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
+                    damping, stiffness, progressValues);
         }
         return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues);
     }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 3b5fd59..c6d62f8 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -258,7 +258,7 @@
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
-        mPageIndicator.setScroll(l, mMaxScrollX);
+        mPageIndicator.setScroll(l, mMaxScroll);
     }
 
     /**
diff --git a/src/com/android/launcher3/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java
new file mode 100644
index 0000000..1349eff
--- /dev/null
+++ b/src/com/android/launcher3/model/PagedViewOrientedState.java
@@ -0,0 +1,104 @@
+/*
+ *
+ *  * Copyright (C) 2020 The Android Open Source Project
+ *  *
+ *  * Licensed under the Apache License, Version 2.0 (the "License");
+ *  * you may not use this file except in compliance with the License.
+ *  * You may obtain a copy of the License at
+ *  *
+ *  *      http://www.apache.org/licenses/LICENSE-2.0
+ *  *
+ *  * Unless required by applicable law or agreed to in writing, software
+ *  * distributed under the License is distributed on an "AS IS" BASIS,
+ *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  * See the License for the specific language governing permissions and
+ *  * limitations under the License.
+ *
+ */
+
+package com.android.launcher3.model;
+
+import android.view.Surface;
+
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.touch.LandscapePagedViewHandler;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.SeascapePagedViewHandler;
+
+/**
+ * Container to hold orientation/rotation related information for Launcher.
+ * This is not meant to be an abstraction layer for applying different functionality between
+ * the different orientation/rotations. For that see {@link PagedOrientationHandler}
+ *
+ * This class has initial default state assuming the device and foreground app have
+ * no ({@link Surface.ROTATION_0} rotation.
+ *
+ * Currently this class resides in {@link com.android.launcher3.PagedView}, but there's a ticket
+ * to disassociate it from Launcher since it's needed before Launcher is instantiated
+ * See TODO(b/150300347)
+ */
+public final class PagedViewOrientedState {
+
+    private PagedOrientationHandler mOrientationHandler = new PortraitPagedViewHandler();
+
+    private int mTouchRotation = Surface.ROTATION_0;
+    private int mDisplayRotation = Surface.ROTATION_0;
+    /**
+     * If {@code true} we default to {@link PortraitPagedViewHandler} and don't support any fake
+     * launcher orientations.
+     */
+    private boolean mDisableMultipleOrientations;
+
+    public void update(int touchRotation, int displayRotation) {
+        mDisplayRotation = displayRotation;
+        mTouchRotation = touchRotation;
+        if (mTouchRotation == Surface.ROTATION_90) {
+            mOrientationHandler = new LandscapePagedViewHandler();
+        } else if (mTouchRotation == Surface.ROTATION_270) {
+            mOrientationHandler = new SeascapePagedViewHandler();
+        } else {
+            mOrientationHandler = new PortraitPagedViewHandler();
+        }
+    }
+
+    /**
+     * @return {@code true} if the area where the user touched the nav bar is the expected
+     * location for the given display rotation. Ex. bottom of phone in portrait, or left side of
+     * phone in landscape, right side in seascape, etc.
+     * False otherwise
+     */
+    public boolean isTouchRegionNaturalForDisplay() {
+        return mTouchRotation == mDisplayRotation;
+    }
+
+    public boolean areMultipleLayoutOrientationsDisabled() {
+        return mDisableMultipleOrientations;
+    }
+
+    public void disableMultipleOrientations(boolean disable) {
+        mDisableMultipleOrientations = disable;
+        if (disable) {
+            mOrientationHandler = new PortraitPagedViewHandler();
+        }
+    }
+
+    public int getDisplayRotation() {
+        return mDisplayRotation;
+    }
+
+    /**
+     * Gets the difference between the rotation of the device/display and which region the
+     * user is currently interacting with in factors of 90 degree clockwise rotations.
+     * Ex. Display is in portrait -> 0, user touches landscape region -> 1, this
+     * method would return 3 because it takes 3 clockwise 90 degree rotations from normal to
+     * landscape (portrait -> seascape -> reverse portrait -> landscape)
+     */
+    public int getTouchDisplayDelta() {
+        return RotationHelper.deltaRotation(mTouchRotation, mDisplayRotation);
+    }
+
+    public PagedOrientationHandler getOrientationHandler() {
+        return mOrientationHandler;
+    }
+}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 812268a..b193ffd 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -176,7 +176,7 @@
 
     // SingleAxisSwipeDetector.Listener's
     @Override
-    public void onDragStart(boolean start) { }
+    public void onDragStart(boolean start, float startDisplacement) { }
 
 
     @Override
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 852928b..3b134b0 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -17,15 +17,26 @@
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
+import android.content.ContentResolver;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.provider.Settings;
+import android.view.MotionEvent;
+import android.view.Surface;
 import android.view.WindowManager;
 
 import com.android.launcher3.Launcher;
+import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -38,6 +49,8 @@
 
     public static final String ALLOW_ROTATION_PREFERENCE_KEY = "pref_allowRotation";
 
+    public static final String FIXED_ROTATION_TRANSFORM_SETTING_NAME = "fixed_rotation_transform";
+
     public static boolean getAllowRotationDefaultValue() {
         // If the device was scaled, used the original dimensions to determine if rotation
         // is allowed of not.
@@ -92,6 +105,18 @@
         } else {
             mPrefs = null;
         }
+
+        // TODO(b/150260456) Add this in home settings as well
+        final ContentResolver resolver = launcher.getContentResolver();
+        final ContentObserver observer = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
+            @Override
+            public void onChange(boolean selfChange) {
+                PagedView.sFlagForcedRotation = Utilities.isForcedRotation(mLauncher);
+            }
+        };
+        resolver.registerContentObserver(Settings.Global.getUriFor(
+            FIXED_ROTATION_TRANSFORM_SETTING_NAME),
+            false, observer);
     }
 
     public void setRotationHadDifferentUI(boolean rotationHasDifferentUI) {
@@ -204,6 +229,118 @@
         }
     }
 
+    public static int getDegreesFromRotation(int rotation) {
+        int degrees;
+        switch (rotation) {
+            case Surface.ROTATION_90:
+                degrees = 90;
+                break;
+            case Surface.ROTATION_180:
+                degrees = 180;
+                break;
+            case Surface.ROTATION_270:
+                degrees = 270;
+                break;
+            case Surface.ROTATION_0:
+            default:
+                degrees = 0;
+                break;
+        }
+        return degrees;
+    }
+
+    public static int getRotationFromDegrees(int degrees) {
+        int threshold = 70;
+        if (degrees >= (360 - threshold) || degrees < (threshold)) {
+            return Surface.ROTATION_0;
+        } else if (degrees < (90 + threshold)) {
+            return Surface.ROTATION_270;
+        } else if (degrees < 180 + threshold) {
+            return Surface.ROTATION_180;
+        } else {
+            return Surface.ROTATION_90;
+        }
+    }
+
+    /**
+     * @return how many factors {@param newRotation} is rotated 90 degrees clockwise.
+     * E.g. 1->Rotated by 90 degrees clockwise, 2->Rotated 180 clockwise...
+     * A value of 0 means no rotation has been applied
+     */
+    public static int deltaRotation(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        return delta;
+    }
+
+    /**
+     * Creates a matrix to transform the given motion event specified by degrees.
+     * If {@param inverse} is {@code true}, the inverse of that matrix will be applied
+     */
+    public static void transformEvent(int degrees, MotionEvent ev, boolean inverse) {
+        Matrix transform = new Matrix();
+        transform.setRotate(degrees);
+        if (inverse) {
+            Matrix inv = new Matrix();
+            transform.invert(inv);
+            ev.transform(inv);
+        } else {
+            ev.transform(transform);
+        }
+        // TODO: Add scaling back in based on degrees
+//        if (getWidth() > 0 && getHeight() > 0) {
+//            float scale = ((float) getWidth()) / getHeight();
+//            transform.postScale(scale, 1 / scale);
+//        }
+    }
+
+    /**
+     * TODO(b/149658423): Have {@link com.android.quickstep.OrientationTouchTransformer
+     *   also use this}
+     */
+    public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
+        Matrix m = new Matrix();
+        switch (displayRotation) {
+            case Surface.ROTATION_0:
+                return m;
+            case Surface.ROTATION_90:
+                m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
+                m.postTranslate(0, screenWidth);
+                break;
+            case Surface.ROTATION_270:
+                m.setRotate(360 - RotationHelper.getDegreesFromRotation(displayRotation));
+                m.postTranslate(screenHeight, 0);
+                break;
+        }
+        return m;
+    }
+
+    public static void mapRectFromNormalOrientation(RectF src, int screenWidth, int screenHeight,
+        int displayRotation) {
+        Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
+        m.mapRect(src);
+    }
+
+    public static void mapInverseRectFromNormalOrientation(RectF src, int screenWidth,
+        int screenHeight, int displayRotation) {
+        Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
+        Matrix inverse = new Matrix();
+        m.invert(inverse);
+        inverse.mapRect(src);
+    }
+
+    public static void getTargetRectForRotation(Rect srcOut, int screenWidth, int screenHeight,
+        int displayRotation) {
+        RectF wrapped = new RectF(srcOut);
+        Matrix m = RotationHelper.getRotationMatrix(screenWidth, screenHeight, displayRotation);
+        m.mapRect(wrapped);
+        wrapped.round(srcOut);
+    }
+
+    public static boolean isRotationLandscape(int rotation) {
+        return rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90;
+    }
+
     @Override
     public String toString() {
         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d,"
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 9df6241..34d69e9 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -230,7 +230,7 @@
     }
 
     @Override
-    public void onDragStart(boolean start) {
+    public void onDragStart(boolean start, float startDisplacement) {
         mStartState = mLauncher.getStateManager().getState();
         mIsLogContainerSet = false;
         if (mCurrentAnimation == null) {
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 30283da..1276ece 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -236,7 +236,7 @@
         } else {
             mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
             mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
-        }
+        } 
     }
 
     protected abstract boolean shouldScrollStart(PointF displacement);
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
new file mode 100644
index 0000000..1db65b9
--- /dev/null
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.util.OverScroller;
+
+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;
+
+public class LandscapePagedViewHandler implements PagedOrientationHandler {
+
+    @Override
+    public int getPrimaryValue(int x, int y) {
+        return y;
+    }
+
+    @Override
+    public int getSecondaryValue(int x, int y) {
+        return x;
+    }
+
+    @Override
+    public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int minMaxScroll) {
+        pagedView.superScrollTo(secondaryScroll, minMaxScroll);
+    }
+
+    @Override
+    public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) {
+        pagedView.scrollTo(pagedView.getScrollX() + x, unboundedScroll + y);
+    }
+
+    @Override
+    public void scrollerStartScroll(OverScroller scroller, int newPosition) {
+        scroller.startScroll(scroller.getCurrPos(), newPosition - scroller.getCurrPos());
+    }
+
+    @Override
+    public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) {
+        int scroll = pagedView.getScrollY();
+        final int halfPageSize = pagedView.getNormalChildHeight() / 2;
+        final int screenCenter = mInsets.top + pagedView.getPaddingTop() + scroll + halfPageSize;
+        final int halfScreenSize = pagedView.getMeasuredHeight() / 2;
+        return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize);
+    }
+
+    @Override
+    public boolean isGoingUp(float displacement) {
+        return displacement > 0;
+    }
+
+    @Override
+    public void adjustFloatingIconStartVelocity(PointF velocity) {
+        float oldX = velocity.x;
+        float oldY = velocity.y;
+        velocity.set(-oldY, oldX);
+    }
+
+    @Override
+    public void delegateScrollTo(PagedView pagedView, int primaryScroll) {
+        pagedView.superScrollTo(pagedView.getScrollX(), primaryScroll);
+    }
+
+    @Override
+    public <T> void set(T target, Int2DAction<T> action, int param) {
+        action.call(target, 0, param);
+    }
+
+    @Override
+    public <T> void set(T target, Float2DAction<T> action, float param) {
+        action.call(target, 0, param);
+    }
+
+    @Override
+    public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
+        return event.getY(pointerIndex);
+    }
+
+    @Override
+    public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
+        return velocityTracker.getYVelocity(pointerId);
+    }
+
+    @Override
+    public int getMeasuredSize(View view) {
+        return view.getMeasuredHeight();
+    }
+
+    @Override
+    public int getPrimarySize(Rect rect) {
+        return rect.height();
+    }
+
+    @Override
+    public float getPrimarySize(RectF rect) {
+        return rect.height();
+    }
+
+    @Override
+    public int getSecondaryDimension(View view) {
+        return view.getWidth();
+    }
+
+    @Override
+    public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
+        float offscreenTranslationY = dp.heightPx - view.getPaddingTop();
+        return new ScaleAndTranslation(1f, 0f, offscreenTranslationY);
+    }
+
+    @Override
+    public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
+        return scaleAndTranslation.translationY;
+    }
+
+    @Override
+    public FloatProperty<View> getPrimaryViewTranslate() {
+        return VIEW_TRANSLATE_Y;
+    }
+
+    @Override
+    public FloatProperty<View> getSecondaryViewTranslate() {
+        return VIEW_TRANSLATE_X;
+    }
+
+    @Override
+    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
+        view.setTranslationX(0);
+        view.setTranslationY(translation);
+    }
+
+    @Override
+    public float getViewCenterPosition(View view) {
+        return view.getTop() + view.getTranslationY();
+    }
+
+    @Override
+    public int getPrimaryScroll(View view) {
+        return view.getScrollY();
+    }
+
+    @Override
+    public float getPrimaryScale(View view) {
+        return view.getScaleY();
+    }
+
+    @Override
+    public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
+        event.setMaxScrollY(maxScroll);
+    }
+
+    @Override
+    public boolean getRecentsRtlSetting(Resources resources) {
+        return !Utilities.isRtl(resources);
+    }
+
+    @Override
+    public float getDegreesRotated() {
+        return 90;
+    }
+
+    @Override
+    public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+        if (displayRotation == Surface.ROTATION_0) {
+            rect.offset(0, value);
+        } else if (displayRotation == Surface.ROTATION_90) {
+            rect.offset(value, 0);
+        } else if (displayRotation == Surface.ROTATION_180) {
+            rect.offset(0, -value);
+        } else {
+            rect.offset(-value, 0);
+        }
+    }
+
+    @Override
+    public int getChildStart(View view) {
+        return view.getTop();
+    }
+
+    @Override
+    public int getCenterForPage(View view, Rect insets) {
+        return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left
+            - insets.right - view.getPaddingRight()) / 2;
+    }
+
+    @Override
+    public int getScrollOffsetStart(View view, Rect insets) {
+        return insets.top + view.getPaddingTop();
+    }
+
+    @Override
+    public int getScrollOffsetEnd(View view, Rect insets) {
+        return view.getHeight() - view.getPaddingBottom() - insets.bottom;
+    }
+
+    @Override
+    public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() {
+        return HORIZONTAL;
+    }
+
+    @Override
+    public int getShortEdgeLength(DeviceProfile dp) {
+        return dp.heightPx;
+    }
+
+    @Override
+    public int getTaskDismissDirectionFactor() {
+        return 1;
+    }
+
+    @Override
+    public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
+        boolean layoutChild) {
+        final int childHeight = child.getMeasuredHeight();
+        final int childBottom = childStart + childHeight;
+        final int childWidth = child.getMeasuredWidth();
+        final int childLeft = pageCenter - childWidth/ 2;
+        if (layoutChild) {
+            child.layout(childLeft, childStart, childLeft + childWidth, childBottom);
+        }
+        return new ChildBounds(childHeight, childWidth, childBottom, childLeft);
+    }
+}
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
new file mode 100644
index 0000000..b4802cd
--- /dev/null
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.util.OverScroller;
+
+/**
+ * Abstraction layer to separate horizontal and vertical specific implementations
+ * for {@link com.android.launcher3.PagedView}. Majority of these implementations are (should be) as
+ * simple as choosing the correct X and Y analogous methods.
+ */
+public interface PagedOrientationHandler {
+
+    interface Int2DAction<T> {
+        void call(T target, int x, int y);
+    }
+    interface Float2DAction<T> {
+        void call(T target, float x, float y);
+    }
+    Int2DAction<View> VIEW_SCROLL_BY = View::scrollBy;
+    Int2DAction<View> VIEW_SCROLL_TO = View::scrollTo;
+    Float2DAction<Canvas> CANVAS_TRANSLATE = Canvas::translate;
+    <T> void set(T target, Int2DAction<T> action, int param);
+    <T> void set(T target, Float2DAction<T> action, float param);
+    float getPrimaryDirection(MotionEvent event, int pointerIndex);
+    float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
+    int getMeasuredSize(View view);
+    int getPrimarySize(Rect rect);
+    float getPrimarySize(RectF rect);
+    int getSecondaryDimension(View view);
+    LauncherState.ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view);
+    float getTranslationValue(LauncherState.ScaleAndTranslation scaleAndTranslation);
+    FloatProperty<View> getPrimaryViewTranslate();
+    FloatProperty<View> getSecondaryViewTranslate();
+    void setPrimaryAndResetSecondaryTranslate(View view, float translation);
+    float getViewCenterPosition(View view);
+    int getPrimaryScroll(View view);
+    float getPrimaryScale(View view);
+    int getChildStart(View view);
+    int getCenterForPage(View view, Rect insets);
+    int getScrollOffsetStart(View view, Rect insets);
+    int getScrollOffsetEnd(View view, Rect insets);
+    SingleAxisSwipeDetector.Direction getOppositeSwipeDirection();
+    int getShortEdgeLength(DeviceProfile dp);
+    int getTaskDismissDirectionFactor();
+    ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
+    void setMaxScroll(AccessibilityEvent event, int maxScroll);
+    boolean getRecentsRtlSetting(Resources resources);
+    float getDegreesRotated();
+    void offsetTaskRect(RectF rect, float value, int delta);
+    int getPrimaryValue(int x, int y);
+    int getSecondaryValue(int x, int y);
+    void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
+    /** Uses {@params pagedView}.getScroll[X|Y]() method for the secondary amount*/
+    void delegateScrollTo(PagedView pagedView, int primaryScroll);
+    void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y);
+    void scrollerStartScroll(OverScroller scroller, int newPosition);
+    CurveProperties getCurveProperties(PagedView pagedView, Rect insets);
+    boolean isGoingUp(float displacement);
+
+    /**
+     * Maps the velocity from the coordinate plane of the foreground app to that
+     * of Launcher's (which now will always be portrait)
+     */
+    void adjustFloatingIconStartVelocity(PointF velocity);
+
+    class CurveProperties {
+        public final int scroll;
+        public final int halfPageSize;
+        public final int screenCenter;
+        public final int halfScreenSize;
+
+        public CurveProperties(int scroll, int halfPageSize, int screenCenter, int halfScreenSize) {
+            this.scroll = scroll;
+            this.halfPageSize = halfPageSize;
+            this.screenCenter = screenCenter;
+            this.halfScreenSize = halfScreenSize;
+        }
+    }
+
+    class ChildBounds {
+
+        public final int primaryDimension;
+        public final int secondaryDimension;
+        public final int childPrimaryEnd;
+        public final int childSecondaryEnd;
+
+        ChildBounds(int primaryDimension, int secondaryDimension, int childPrimaryEnd,
+            int childSecondaryEnd) {
+            this.primaryDimension = primaryDimension;
+            this.secondaryDimension = secondaryDimension;
+            this.childPrimaryEnd = childPrimaryEnd;
+            this.childSecondaryEnd = childSecondaryEnd;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
new file mode 100644
index 0000000..22eee49
--- /dev/null
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.PagedView;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.util.OverScroller;
+
+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.VERTICAL;
+
+public class PortraitPagedViewHandler implements PagedOrientationHandler {
+
+    @Override
+    public int getPrimaryValue(int x, int y) {
+        return x;
+    }
+
+    @Override
+    public int getSecondaryValue(int x, int y) {
+        return y;
+    }
+
+    @Override
+    public void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll) {
+        pagedView.superScrollTo(primaryScroll, secondaryScroll);
+    }
+
+    @Override
+    public void delegateScrollBy(PagedView pagedView, int unboundedScroll, int x, int y) {
+        pagedView.scrollTo(unboundedScroll + x, pagedView.getScrollY() + y);
+    }
+
+    @Override
+    public void scrollerStartScroll(OverScroller scroller, int newPosition) {
+        scroller.startScroll(newPosition - scroller.getCurrPos(), scroller.getCurrPos());
+    }
+
+    @Override
+    public CurveProperties getCurveProperties(PagedView pagedView, Rect mInsets) {
+        int scroll = pagedView.getScrollX();
+        final int halfPageSize = pagedView.getNormalChildWidth() / 2;
+        final int screenCenter = mInsets.left + pagedView.getPaddingLeft() + scroll + halfPageSize;
+        final int halfScreenSize = pagedView.getMeasuredWidth() / 2;
+        return new CurveProperties(scroll, halfPageSize, screenCenter, halfScreenSize);
+    }
+
+    @Override
+    public boolean isGoingUp(float displacement) {
+        return displacement < 0;
+    }
+
+    @Override
+    public void adjustFloatingIconStartVelocity(PointF velocity) {
+        //no-op
+    }
+
+    @Override
+    public void delegateScrollTo(PagedView pagedView, int primaryScroll) {
+        pagedView.superScrollTo(primaryScroll, pagedView.getScrollY());
+    }
+
+    @Override
+    public <T> void set(T target, Int2DAction<T> action, int param) {
+        action.call(target, param, 0);
+    }
+
+    @Override
+    public <T> void set(T target, Float2DAction<T> action, float param) {
+        action.call(target, param, 0);
+    }
+
+    @Override
+    public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
+        return event.getX(pointerIndex);
+    }
+
+    @Override
+    public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) {
+        return velocityTracker.getXVelocity(pointerId);
+    }
+
+    @Override
+    public int getMeasuredSize(View view) {
+        return view.getMeasuredWidth();
+    }
+
+    @Override
+    public int getPrimarySize(Rect rect) {
+        return rect.width();
+    }
+
+    @Override
+    public float getPrimarySize(RectF rect) {
+        return rect.width();
+    }
+
+    @Override
+    public int getSecondaryDimension(View view) {
+        return view.getHeight();
+    }
+
+    @Override
+    public ScaleAndTranslation getScaleAndTranslation(DeviceProfile dp, View view) {
+        float offscreenTranslationX = dp.widthPx - view.getPaddingStart();
+        return new ScaleAndTranslation(1f, offscreenTranslationX, 0f);
+    }
+
+    @Override
+    public float getTranslationValue(ScaleAndTranslation scaleAndTranslation) {
+        return scaleAndTranslation.translationX;
+    }
+
+    @Override
+    public FloatProperty<View> getPrimaryViewTranslate() {
+        return VIEW_TRANSLATE_X;
+    }
+
+    @Override
+    public FloatProperty<View> getSecondaryViewTranslate() {
+        return VIEW_TRANSLATE_Y;
+    }
+
+    @Override
+    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
+        view.setTranslationX(translation);
+        view.setTranslationY(0);
+    }
+
+    @Override
+    public float getViewCenterPosition(View view) {
+        return view.getLeft() + view.getTranslationX();
+    }
+
+    @Override
+    public int getPrimaryScroll(View view) {
+        return view.getScrollX();
+    }
+
+    @Override
+    public float getPrimaryScale(View view) {
+        return view.getScaleX();
+    }
+
+    @Override
+    public void setMaxScroll(AccessibilityEvent event, int maxScroll) {
+        event.setMaxScrollX(maxScroll);
+    }
+
+    @Override
+    public boolean getRecentsRtlSetting(Resources resources) {
+        return !Utilities.isRtl(resources);
+    }
+
+    @Override
+    public float getDegreesRotated() {
+        return 0;
+    }
+
+    @Override
+    public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+        if (displayRotation == Surface.ROTATION_0) {
+            rect.offset(value, 0);
+        } else if (displayRotation == Surface.ROTATION_90) {
+            rect.offset(0, -value);
+        } else if (displayRotation == Surface.ROTATION_180) {
+            rect.offset(-value, 0);
+        } else {
+            rect.offset(0, value);
+        }
+    }
+
+    @Override
+    public int getChildStart(View view) {
+        return view.getLeft();
+    }
+
+    @Override
+    public int getCenterForPage(View view, Rect insets) {
+        return (view.getPaddingTop() + view.getMeasuredHeight() + insets.top
+            - insets.bottom - view.getPaddingBottom()) / 2;
+    }
+
+    @Override
+    public int getScrollOffsetStart(View view, Rect insets) {
+        return insets.left + view.getPaddingLeft();
+    }
+
+    @Override
+    public int getScrollOffsetEnd(View view, Rect insets) {
+        return view.getWidth() - view.getPaddingRight() - insets.right;
+    }
+
+    @Override
+    public SingleAxisSwipeDetector.Direction getOppositeSwipeDirection() {
+        return VERTICAL;
+    }
+
+    @Override
+    public int getShortEdgeLength(DeviceProfile dp) {
+        return dp.widthPx;
+    }
+
+    @Override
+    public int getTaskDismissDirectionFactor() {
+        return -1;
+    }
+
+    @Override
+    public ChildBounds getChildBounds(View child, int childStart, int pageCenter,
+        boolean layoutChild) {
+        final int childWidth = child.getMeasuredWidth();
+        final int childRight = childStart + childWidth;
+        final int childHeight = child.getMeasuredHeight();
+        final int childTop = pageCenter - childHeight / 2;
+        if (layoutChild) {
+            child.layout(childStart, childTop, childRight, childTop + childHeight);
+        }
+        return new ChildBounds(childWidth, childHeight, childRight, childTop);
+    }
+}
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
new file mode 100644
index 0000000..eebd87f
--- /dev/null
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.touch;
+
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.view.Surface;
+
+import com.android.launcher3.Utilities;
+
+public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
+
+    @Override
+    public int getTaskDismissDirectionFactor() {
+        return -1;
+    }
+
+    @Override
+    public boolean getRecentsRtlSetting(Resources resources) {
+        return Utilities.isRtl(resources);
+    }
+
+    @Override
+    public void offsetTaskRect(RectF rect, float value, int displayRotation) {
+        if (displayRotation == Surface.ROTATION_0) {
+            rect.offset(0, value);
+        } else if (displayRotation == Surface.ROTATION_90) {
+            rect.offset(value, 0);
+        } else if (displayRotation == Surface.ROTATION_180) {
+            rect.offset(0, -value);
+        } else {
+            rect.offset(-value, 0);
+        }
+    }
+
+    @Override
+    public float getDegreesRotated() {
+        return 270;
+    }
+
+    @Override
+    public boolean isGoingUp(float displacement) {
+        return displacement < 0;
+    }
+
+    @Override
+    public void adjustFloatingIconStartVelocity(PointF velocity) {
+        float oldX = velocity.x;
+        float oldY = velocity.y;
+        velocity.set(oldY, -oldX);
+    }
+}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index 9d406f3..d725486 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -148,7 +148,8 @@
 
     @Override
     protected void reportDragStartInternal(boolean recatch) {
-        mListener.onDragStart(!recatch);
+        float startDisplacement = mDir.extractDirection(mSubtractDisplacement);
+        mListener.onDragStart(!recatch, startDisplacement);
     }
 
     @Override
@@ -165,8 +166,13 @@
 
     /** Listener to receive updates on the swipe. */
     public interface Listener {
-        /** @param start whether this was the original drag start, as opposed to a recatch. */
-        void onDragStart(boolean start);
+        /**
+         * TODO(b/150256055) consolidate all the different onDrag() methods into one
+         * @param start whether this was the original drag start, as opposed to a recatch.
+         * @param startDisplacement the initial touch displacement for the primary direction as
+         *        given by by {@link Direction#extractDirection(PointF)}
+         */
+        void onDragStart(boolean start, float startDisplacement);
 
         boolean onDrag(float displacement);
 
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index d3dac04..f18e411 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -28,6 +28,8 @@
 import android.view.Display;
 import android.view.WindowManager;
 
+import androidx.annotation.VisibleForTesting;
+
 import java.util.ArrayList;
 
 /**
@@ -127,6 +129,18 @@
 
         public final DisplayMetrics metrics;
 
+        @VisibleForTesting
+        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
+                Point largestSize, DisplayMetrics metrics) {
+            this.id = id;
+            this.rotation = rotation;
+            this.singleFrameMs = singleFrameMs;
+            this.realSize = realSize;
+            this.smallestSize = smallestSize;
+            this.largestSize = largestSize;
+            this.metrics = metrics;
+        }
+
         private Info(Context context) {
             this(context.getSystemService(WindowManager.class).getDefaultDisplay());
         }
diff --git a/src/com/android/launcher3/util/DynamicResource.java b/src/com/android/launcher3/util/DynamicResource.java
index 8a75767..e6ee186 100644
--- a/src/com/android/launcher3/util/DynamicResource.java
+++ b/src/com/android/launcher3/util/DynamicResource.java
@@ -69,6 +69,11 @@
     }
 
     @Override
+    public float getFloat(@DimenRes int resId) {
+        return mContext.getResources().getFloat(resId);
+    }
+
+    @Override
     public void onPluginConnected(ResourceProvider plugin, Context context) {
         mPlugin = plugin;
     }
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
index fc8a138..3c398b8 100644
--- a/src/com/android/launcher3/util/OverScroller.java
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -26,11 +26,13 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
-import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.FloatPropertyCompat;
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
+import com.android.launcher3.R;
+import com.android.systemui.plugins.ResourceProvider;
+
 /**
  * Based on {@link android.widget.OverScroller} supporting only 1-d scrolling and with more
  * customization options.
@@ -425,6 +427,7 @@
         // Current state of the animation.
         private int mState = SPLINE;
 
+        private Context mContext;
         private SpringAnimation mSpring;
 
         // Constant gravity value, used in the deceleration phase.
@@ -500,6 +503,7 @@
         }
 
         SplineOverScroller(Context context) {
+            mContext = context;
             mFinished = true;
             final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
             mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
@@ -560,9 +564,12 @@
                 }
                 mSpring = new SpringAnimation(this, SPRING_PROPERTY);
 
+                ResourceProvider rp = DynamicResource.provider(mContext);
+                float stiffness = rp.getFloat(R.dimen.horizontal_spring_stiffness);
+                float damping = rp.getFloat(R.dimen.horizontal_spring_damping_ratio);
                 mSpring.setSpring(new SpringForce(mFinal)
-                        .setStiffness(SpringForce.STIFFNESS_LOW)
-                        .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+                        .setStiffness(stiffness)
+                        .setDampingRatio(damping));
                 mSpring.setStartVelocity(velocity);
                 mSpring.animateToFinalPosition(mFinal);
                 mSpring.addEndListener((animation, canceled, value, velocity1) -> {
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index bdba39c..11c1029 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -149,8 +149,7 @@
     /* SingleAxisSwipeDetector.Listener */
 
     @Override
-    public void onDragStart(boolean start) {
-    }
+    public void onDragStart(boolean start, float startDisplacement) { }
 
     @Override
     public boolean onDrag(float displacement) {
diff --git a/src_plugins/com/android/systemui/plugins/ResourceProvider.java b/src_plugins/com/android/systemui/plugins/ResourceProvider.java
index eaed9e7..d1767a0 100644
--- a/src_plugins/com/android/systemui/plugins/ResourceProvider.java
+++ b/src_plugins/com/android/systemui/plugins/ResourceProvider.java
@@ -44,4 +44,9 @@
      * @see android.content.res.Resources#getColor(int)
      */
     int getColor(int resId);
+
+    /**
+     * @see android.content.res.Resources#getFloat(int)
+     */
+    float getFloat(int resId);
 }
diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index b0ece77..472e1a1 100644
--- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -89,7 +89,7 @@
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 - mTouchSlop);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDragStart(anyBoolean());
+        verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
     }
 
     @Test
@@ -99,7 +99,7 @@
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100, 100 + mTouchSlop);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDragStart(anyBoolean());
+        verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
     }
 
     @Test
@@ -107,7 +107,7 @@
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 + mTouchSlop, 100);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener, never()).onDragStart(anyBoolean());
+        verify(mMockListener, never()).onDragStart(anyBoolean(), anyFloat());
     }
 
     @Test
@@ -118,7 +118,7 @@
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 + mTouchSlop, 100);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDragStart(anyBoolean());
+        verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
     }
 
     @Test
@@ -129,7 +129,7 @@
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 - mTouchSlop, 100);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDragStart(anyBoolean());
+        verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
     }
 
     @Test
@@ -140,7 +140,7 @@
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 - mTouchSlop, 100);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDragStart(anyBoolean());
+        verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
     }
 
     @Test
@@ -151,7 +151,7 @@
         mGenerator.put(0, 100, 100);
         mGenerator.move(0, 100 + mTouchSlop, 100);
         // TODO: actually calculate the following parameters and do exact value checks.
-        verify(mMockListener).onDragStart(anyBoolean());
+        verify(mMockListener).onDragStart(anyBoolean(), anyFloat());
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
index 33a6cf9..1dbba6a 100644
--- a/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/SimpleActivityRule.java
@@ -64,6 +64,7 @@
                 mBase.evaluate();
             } finally {
                 app.unregisterActivityLifecycleCallbacks(this);
+                mActivity = null;
             }
         }