Add vertical layout support for Overview in portrait

WM is making changes which allows apps to maintain
their orientation independent of the orientation of
the foreground app. This allows recents to always start
in portrait even when the app currently running is in
landscape. This means we have to give the illusion of
a landscape oriented overview when user swipes up in
gesterual nav when launcher is started in portrait
configuration.

PagedOrientationHandler abstracts all coordinate specific
logic from Paged/RecentsView primarily, but also all
other dynamic calculations throughout launcher.
PagedViewOrientationState is the single point of exposure
to other classes that depend on those changes. The goal
is to also minimize holding state to allow for default
implementations of PagedOrientationHandler for all the
3p/Fallback classes. PagedViewOrientationState also
holds other data around rotation that isn't
specifically tied to view logic.

The fake landscape overview can be toggled with:
adb shell settings put global forced_rotation [0/1]

Fixes: 146176182
Change-Id: I65d8d4e9f92b93931cbe0053ccaf0cda8d2ffd6c
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();