Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.launcher3;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.LayoutTransition;
     23 import android.animation.ObjectAnimator;
     24 import android.animation.TimeInterpolator;
     25 import android.annotation.TargetApi;
     26 import android.content.Context;
     27 import android.content.res.TypedArray;
     28 import android.graphics.Canvas;
     29 import android.graphics.Matrix;
     30 import android.graphics.Rect;
     31 import android.os.Build;
     32 import android.os.Bundle;
     33 import android.os.Parcel;
     34 import android.os.Parcelable;
     35 import android.util.AttributeSet;
     36 import android.util.DisplayMetrics;
     37 import android.util.Log;
     38 import android.view.InputDevice;
     39 import android.view.KeyEvent;
     40 import android.view.MotionEvent;
     41 import android.view.VelocityTracker;
     42 import android.view.View;
     43 import android.view.ViewConfiguration;
     44 import android.view.ViewGroup;
     45 import android.view.ViewParent;
     46 import android.view.accessibility.AccessibilityEvent;
     47 import android.view.accessibility.AccessibilityManager;
     48 import android.view.accessibility.AccessibilityNodeInfo;
     49 import android.view.animation.Interpolator;
     50 
     51 import com.android.launcher3.util.LauncherEdgeEffect;
     52 import com.android.launcher3.util.Thunk;
     53 
     54 import java.util.ArrayList;
     55 
     56 /**
     57  * An abstraction of the original Workspace which supports browsing through a
     58  * sequential list of "pages"
     59  */
     60 public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
     61     private static final String TAG = "PagedView";
     62     private static final boolean DEBUG = false;
     63     protected static final int INVALID_PAGE = -1;
     64 
     65     // the min drag distance for a fling to register, to prevent random page shifts
     66     private static final int MIN_LENGTH_FOR_FLING = 25;
     67 
     68     protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
     69     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
     70     protected static final float NANOTIME_DIV = 1000000000.0f;
     71 
     72     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
     73     // The page is moved more than halfway, automatically move to the next page on touch up.
     74     private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
     75 
     76     private static final float MAX_SCROLL_PROGRESS = 1.0f;
     77 
     78     // The following constants need to be scaled based on density. The scaled versions will be
     79     // assigned to the corresponding member variables below.
     80     private static final int FLING_THRESHOLD_VELOCITY = 500;
     81     private static final int MIN_SNAP_VELOCITY = 1500;
     82     private static final int MIN_FLING_VELOCITY = 250;
     83 
     84     public static final int INVALID_RESTORE_PAGE = -1001;
     85 
     86     private boolean mFreeScroll = false;
     87     private int mFreeScrollMinScrollX = -1;
     88     private int mFreeScrollMaxScrollX = -1;
     89 
     90     static final int AUTOMATIC_PAGE_SPACING = -1;
     91 
     92     protected int mFlingThresholdVelocity;
     93     protected int mMinFlingVelocity;
     94     protected int mMinSnapVelocity;
     95 
     96     protected float mDensity;
     97     protected float mSmoothingTime;
     98     protected float mTouchX;
     99 
    100     protected boolean mFirstLayout = true;
    101     private int mNormalChildHeight;
    102 
    103     protected int mCurrentPage;
    104     protected int mRestorePage = INVALID_RESTORE_PAGE;
    105     protected int mChildCountOnLastLayout;
    106 
    107     protected int mNextPage = INVALID_PAGE;
    108     protected int mMaxScrollX;
    109     protected LauncherScroller mScroller;
    110     private Interpolator mDefaultInterpolator;
    111     private VelocityTracker mVelocityTracker;
    112     @Thunk int mPageSpacing = 0;
    113 
    114     private float mParentDownMotionX;
    115     private float mParentDownMotionY;
    116     private float mDownMotionX;
    117     private float mDownMotionY;
    118     private float mDownScrollX;
    119     private float mDragViewBaselineLeft;
    120     protected float mLastMotionX;
    121     protected float mLastMotionXRemainder;
    122     protected float mLastMotionY;
    123     protected float mTotalMotionX;
    124     private int mLastScreenCenter = -1;
    125 
    126     private boolean mCancelTap;
    127 
    128     private int[] mPageScrolls;
    129 
    130     protected final static int TOUCH_STATE_REST = 0;
    131     protected final static int TOUCH_STATE_SCROLLING = 1;
    132     protected final static int TOUCH_STATE_PREV_PAGE = 2;
    133     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
    134     protected final static int TOUCH_STATE_REORDERING = 4;
    135 
    136     protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
    137 
    138     protected int mTouchState = TOUCH_STATE_REST;
    139     protected boolean mForceScreenScrolled = false;
    140 
    141     protected OnLongClickListener mLongClickListener;
    142 
    143     protected int mTouchSlop;
    144     private int mMaximumVelocity;
    145     protected int mPageLayoutWidthGap;
    146     protected int mPageLayoutHeightGap;
    147     protected int mCellCountX = 0;
    148     protected int mCellCountY = 0;
    149     protected boolean mCenterPagesVertically;
    150     protected boolean mAllowOverScroll = true;
    151     protected int[] mTempVisiblePagesRange = new int[2];
    152     protected boolean mForceDrawAllChildrenNextFrame;
    153 
    154     protected static final int INVALID_POINTER = -1;
    155 
    156     protected int mActivePointerId = INVALID_POINTER;
    157 
    158     private PageSwitchListener mPageSwitchListener;
    159 
    160     // If true, modify alpha of neighboring pages as user scrolls left/right
    161     protected boolean mFadeInAdjacentScreens = false;
    162 
    163     protected boolean mIsPageMoving = false;
    164 
    165     private boolean mWasInOverscroll = false;
    166 
    167     // Page Indicator
    168     @Thunk int mPageIndicatorViewId;
    169     @Thunk PageIndicator mPageIndicator;
    170     // The viewport whether the pages are to be contained (the actual view may be larger than the
    171     // viewport)
    172     private Rect mViewport = new Rect();
    173 
    174     // Reordering
    175     // We use the min scale to determine how much to expand the actually PagedView measured
    176     // dimensions such that when we are zoomed out, the view is not clipped
    177     private static int REORDERING_DROP_REPOSITION_DURATION = 200;
    178     @Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
    179     private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
    180 
    181     private float mMinScale = 1f;
    182     private boolean mUseMinScale = false;
    183     protected View mDragView;
    184     private Runnable mSidePageHoverRunnable;
    185     @Thunk int mSidePageHoverIndex = -1;
    186     // This variable's scope is only for the duration of startReordering() and endReordering()
    187     private boolean mReorderingStarted = false;
    188     // This variable's scope is for the duration of startReordering() and after the zoomIn()
    189     // animation after endReordering()
    190     private boolean mIsReordering;
    191     // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
    192     private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
    193     private int mPostReorderingPreZoomInRemainingAnimationCount;
    194     private Runnable mPostReorderingPreZoomInRunnable;
    195 
    196     // Convenience/caching
    197     private static final Matrix sTmpInvMatrix = new Matrix();
    198     private static final float[] sTmpPoint = new float[2];
    199     private static final int[] sTmpIntPoint = new int[2];
    200     private static final Rect sTmpRect = new Rect();
    201 
    202     protected final Rect mInsets = new Rect();
    203     protected final boolean mIsRtl;
    204 
    205     // Edge effect
    206     private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect();
    207     private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect();
    208 
    209     public interface PageSwitchListener {
    210         void onPageSwitch(View newPage, int newPageIndex);
    211     }
    212 
    213     public PagedView(Context context) {
    214         this(context, null);
    215     }
    216 
    217     public PagedView(Context context, AttributeSet attrs) {
    218         this(context, attrs, 0);
    219     }
    220 
    221     public PagedView(Context context, AttributeSet attrs, int defStyle) {
    222         super(context, attrs, defStyle);
    223 
    224         TypedArray a = context.obtainStyledAttributes(attrs,
    225                 R.styleable.PagedView, defStyle, 0);
    226 
    227         mPageLayoutWidthGap = a.getDimensionPixelSize(
    228                 R.styleable.PagedView_pageLayoutWidthGap, 0);
    229         mPageLayoutHeightGap = a.getDimensionPixelSize(
    230                 R.styleable.PagedView_pageLayoutHeightGap, 0);
    231         mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1);
    232         a.recycle();
    233 
    234         setHapticFeedbackEnabled(false);
    235         mIsRtl = Utilities.isRtl(getResources());
    236         init();
    237     }
    238 
    239     /**
    240      * Initializes various states for this workspace.
    241      */
    242     protected void init() {
    243         mScroller = new LauncherScroller(getContext());
    244         setDefaultInterpolator(new ScrollInterpolator());
    245         mCurrentPage = 0;
    246         mCenterPagesVertically = true;
    247 
    248         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
    249         mTouchSlop = configuration.getScaledPagingTouchSlop();
    250         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    251         mDensity = getResources().getDisplayMetrics().density;
    252 
    253         mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
    254         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
    255         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
    256         setOnHierarchyChangeListener(this);
    257         setWillNotDraw(false);
    258     }
    259 
    260     protected void setEdgeGlowColor(int color) {
    261         mEdgeGlowLeft.setColor(color);
    262         mEdgeGlowRight.setColor(color);
    263     }
    264 
    265     protected void setDefaultInterpolator(Interpolator interpolator) {
    266         mDefaultInterpolator = interpolator;
    267         mScroller.setInterpolator(mDefaultInterpolator);
    268     }
    269 
    270     protected void onAttachedToWindow() {
    271         super.onAttachedToWindow();
    272 
    273         // Hook up the page indicator
    274         ViewGroup parent = (ViewGroup) getParent();
    275         ViewGroup grandParent = (ViewGroup) parent.getParent();
    276         if (mPageIndicator == null && mPageIndicatorViewId > -1) {
    277             mPageIndicator = (PageIndicator) grandParent.findViewById(mPageIndicatorViewId);
    278             mPageIndicator.removeAllMarkers(true);
    279 
    280             ArrayList<PageIndicator.PageMarkerResources> markers =
    281                     new ArrayList<PageIndicator.PageMarkerResources>();
    282             for (int i = 0; i < getChildCount(); ++i) {
    283                 markers.add(getPageIndicatorMarker(i));
    284             }
    285 
    286             mPageIndicator.addMarkers(markers, true);
    287 
    288             OnClickListener listener = getPageIndicatorClickListener();
    289             if (listener != null) {
    290                 mPageIndicator.setOnClickListener(listener);
    291             }
    292             mPageIndicator.setContentDescription(getPageIndicatorDescription());
    293         }
    294     }
    295 
    296     protected String getPageIndicatorDescription() {
    297         return getCurrentPageDescription();
    298     }
    299 
    300     protected OnClickListener getPageIndicatorClickListener() {
    301         return null;
    302     }
    303 
    304     @Override
    305     protected void onDetachedFromWindow() {
    306         super.onDetachedFromWindow();
    307         // Unhook the page indicator
    308         mPageIndicator = null;
    309     }
    310 
    311     // Convenience methods to map points from self to parent and vice versa
    312     private float[] mapPointFromViewToParent(View v, float x, float y) {
    313         sTmpPoint[0] = x;
    314         sTmpPoint[1] = y;
    315         v.getMatrix().mapPoints(sTmpPoint);
    316         sTmpPoint[0] += v.getLeft();
    317         sTmpPoint[1] += v.getTop();
    318         return sTmpPoint;
    319     }
    320     private float[] mapPointFromParentToView(View v, float x, float y) {
    321         sTmpPoint[0] = x - v.getLeft();
    322         sTmpPoint[1] = y - v.getTop();
    323         v.getMatrix().invert(sTmpInvMatrix);
    324         sTmpInvMatrix.mapPoints(sTmpPoint);
    325         return sTmpPoint;
    326     }
    327 
    328     private void updateDragViewTranslationDuringDrag() {
    329         if (mDragView != null) {
    330             float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
    331                     (mDragViewBaselineLeft - mDragView.getLeft());
    332             float y = mLastMotionY - mDownMotionY;
    333             mDragView.setTranslationX(x);
    334             mDragView.setTranslationY(y);
    335 
    336             if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
    337                     + x + ", " + y);
    338         }
    339     }
    340 
    341     public void setMinScale(float f) {
    342         mMinScale = f;
    343         mUseMinScale = true;
    344         requestLayout();
    345     }
    346 
    347     @Override
    348     public void setScaleX(float scaleX) {
    349         super.setScaleX(scaleX);
    350         if (isReordering(true)) {
    351             float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
    352             mLastMotionX = p[0];
    353             mLastMotionY = p[1];
    354             updateDragViewTranslationDuringDrag();
    355         }
    356     }
    357 
    358     // Convenience methods to get the actual width/height of the PagedView (since it is measured
    359     // to be larger to account for the minimum possible scale)
    360     int getViewportWidth() {
    361         return mViewport.width();
    362     }
    363     int getViewportHeight() {
    364         return mViewport.height();
    365     }
    366 
    367     // Convenience methods to get the offset ASSUMING that we are centering the pages in the
    368     // PagedView both horizontally and vertically
    369     int getViewportOffsetX() {
    370         return (getMeasuredWidth() - getViewportWidth()) / 2;
    371     }
    372 
    373     int getViewportOffsetY() {
    374         return (getMeasuredHeight() - getViewportHeight()) / 2;
    375     }
    376 
    377     PageIndicator getPageIndicator() {
    378         return mPageIndicator;
    379     }
    380     protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
    381         return new PageIndicator.PageMarkerResources();
    382     }
    383 
    384     /**
    385      * Add a page change listener which will be called when a page is _finished_ listening.
    386      *
    387      */
    388     public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
    389         mPageSwitchListener = pageSwitchListener;
    390         if (mPageSwitchListener != null) {
    391             mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
    392         }
    393     }
    394 
    395     /**
    396      * Returns the index of the currently displayed page.
    397      */
    398     public int getCurrentPage() {
    399         return mCurrentPage;
    400     }
    401 
    402     /**
    403      * Returns the index of page to be shown immediately afterwards.
    404      */
    405     int getNextPage() {
    406         return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
    407     }
    408 
    409     int getPageCount() {
    410         return getChildCount();
    411     }
    412 
    413     public View getPageAt(int index) {
    414         return getChildAt(index);
    415     }
    416 
    417     protected int indexToPage(int index) {
    418         return index;
    419     }
    420 
    421     /**
    422      * Updates the scroll of the current page immediately to its final scroll position.  We use this
    423      * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
    424      * the previous tab page.
    425      */
    426     protected void updateCurrentPageScroll() {
    427         // If the current page is invalid, just reset the scroll position to zero
    428         int newX = 0;
    429         if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
    430             newX = getScrollForPage(mCurrentPage);
    431         }
    432         scrollTo(newX, 0);
    433         mScroller.setFinalX(newX);
    434         forceFinishScroller();
    435     }
    436 
    437     private void abortScrollerAnimation(boolean resetNextPage) {
    438         mScroller.abortAnimation();
    439         // We need to clean up the next page here to avoid computeScrollHelper from
    440         // updating current page on the pass.
    441         if (resetNextPage) {
    442             mNextPage = INVALID_PAGE;
    443         }
    444     }
    445 
    446     private void forceFinishScroller() {
    447         mScroller.forceFinished(true);
    448         // We need to clean up the next page here to avoid computeScrollHelper from
    449         // updating current page on the pass.
    450         mNextPage = INVALID_PAGE;
    451     }
    452 
    453     private int validateNewPage(int newPage) {
    454         int validatedPage = newPage;
    455         // When in free scroll mode, we need to clamp to the free scroll page range.
    456         if (mFreeScroll) {
    457             getFreeScrollPageRange(mTempVisiblePagesRange);
    458             validatedPage = Math.max(mTempVisiblePagesRange[0],
    459                     Math.min(newPage, mTempVisiblePagesRange[1]));
    460         }
    461         // Ensure that it is clamped by the actual set of children in all cases
    462         validatedPage = Math.max(0, Math.min(validatedPage, getPageCount() - 1));
    463         return validatedPage;
    464     }
    465 
    466     /**
    467      * Sets the current page.
    468      */
    469     public void setCurrentPage(int currentPage) {
    470         if (!mScroller.isFinished()) {
    471             abortScrollerAnimation(true);
    472         }
    473         // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
    474         // the default
    475         if (getChildCount() == 0) {
    476             return;
    477         }
    478         mForceScreenScrolled = true;
    479         mCurrentPage = validateNewPage(currentPage);
    480         updateCurrentPageScroll();
    481         notifyPageSwitchListener();
    482         invalidate();
    483     }
    484 
    485     /**
    486      * The restore page will be set in place of the current page at the next (likely first)
    487      * layout.
    488      */
    489     void setRestorePage(int restorePage) {
    490         mRestorePage = restorePage;
    491     }
    492     int getRestorePage() {
    493         return mRestorePage;
    494     }
    495 
    496     /**
    497      * Should be called whenever the page changes. In the case of a scroll, we wait until the page
    498      * has settled.
    499      */
    500     protected void notifyPageSwitchListener() {
    501         if (mPageSwitchListener != null) {
    502             mPageSwitchListener.onPageSwitch(getPageAt(getNextPage()), getNextPage());
    503         }
    504 
    505         updatePageIndicator();
    506     }
    507 
    508     private void updatePageIndicator() {
    509         // Update the page indicator (when we aren't reordering)
    510         if (mPageIndicator != null) {
    511             mPageIndicator.setContentDescription(getPageIndicatorDescription());
    512             if (!isReordering(false)) {
    513                 mPageIndicator.setActiveMarker(getNextPage());
    514             }
    515         }
    516     }
    517     protected void pageBeginMoving() {
    518         if (!mIsPageMoving) {
    519             mIsPageMoving = true;
    520             onPageBeginMoving();
    521         }
    522     }
    523 
    524     protected void pageEndMoving() {
    525         if (mIsPageMoving) {
    526             mIsPageMoving = false;
    527             onPageEndMoving();
    528         }
    529     }
    530 
    531     protected boolean isPageMoving() {
    532         return mIsPageMoving;
    533     }
    534 
    535     // a method that subclasses can override to add behavior
    536     protected void onPageBeginMoving() {
    537     }
    538 
    539     // a method that subclasses can override to add behavior
    540     protected void onPageEndMoving() {
    541         mWasInOverscroll = false;
    542     }
    543 
    544     /**
    545      * Registers the specified listener on each page contained in this workspace.
    546      *
    547      * @param l The listener used to respond to long clicks.
    548      */
    549     @Override
    550     public void setOnLongClickListener(OnLongClickListener l) {
    551         mLongClickListener = l;
    552         final int count = getPageCount();
    553         for (int i = 0; i < count; i++) {
    554             getPageAt(i).setOnLongClickListener(l);
    555         }
    556         super.setOnLongClickListener(l);
    557     }
    558 
    559     @Override
    560     public void scrollBy(int x, int y) {
    561         scrollTo(getScrollX() + x, getScrollY() + y);
    562     }
    563 
    564     @Override
    565     public void scrollTo(int x, int y) {
    566         // In free scroll mode, we clamp the scrollX
    567         if (mFreeScroll) {
    568             // If the scroller is trying to move to a location beyond the maximum allowed
    569             // in the free scroll mode, we make sure to end the scroll operation.
    570             if (!mScroller.isFinished() &&
    571                     (x > mFreeScrollMaxScrollX || x < mFreeScrollMinScrollX)) {
    572                 forceFinishScroller();
    573             }
    574 
    575             x = Math.min(x, mFreeScrollMaxScrollX);
    576             x = Math.max(x, mFreeScrollMinScrollX);
    577         }
    578 
    579         boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
    580         boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
    581         if (isXBeforeFirstPage) {
    582             super.scrollTo(mIsRtl ? mMaxScrollX : 0, y);
    583             if (mAllowOverScroll) {
    584                 mWasInOverscroll = true;
    585                 if (mIsRtl) {
    586                     overScroll(x - mMaxScrollX);
    587                 } else {
    588                     overScroll(x);
    589                 }
    590             }
    591         } else if (isXAfterLastPage) {
    592             super.scrollTo(mIsRtl ? 0 : mMaxScrollX, y);
    593             if (mAllowOverScroll) {
    594                 mWasInOverscroll = true;
    595                 if (mIsRtl) {
    596                     overScroll(x);
    597                 } else {
    598                     overScroll(x - mMaxScrollX);
    599                 }
    600             }
    601         } else {
    602             if (mWasInOverscroll) {
    603                 overScroll(0);
    604                 mWasInOverscroll = false;
    605             }
    606             super.scrollTo(x, y);
    607         }
    608 
    609         mTouchX = x;
    610         mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
    611 
    612         // Update the last motion events when scrolling
    613         if (isReordering(true)) {
    614             float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
    615             mLastMotionX = p[0];
    616             mLastMotionY = p[1];
    617             updateDragViewTranslationDuringDrag();
    618         }
    619     }
    620 
    621     private void sendScrollAccessibilityEvent() {
    622         AccessibilityManager am =
    623                 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
    624         if (am.isEnabled()) {
    625             if (mCurrentPage != getNextPage()) {
    626                 AccessibilityEvent ev =
    627                         AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
    628                 ev.setScrollable(true);
    629                 ev.setScrollX(getScrollX());
    630                 ev.setScrollY(getScrollY());
    631                 ev.setMaxScrollX(mMaxScrollX);
    632                 ev.setMaxScrollY(0);
    633 
    634                 sendAccessibilityEventUnchecked(ev);
    635             }
    636         }
    637     }
    638 
    639     // we moved this functionality to a helper function so SmoothPagedView can reuse it
    640     protected boolean computeScrollHelper() {
    641         if (mScroller.computeScrollOffset()) {
    642             // Don't bother scrolling if the page does not need to be moved
    643             if (getScrollX() != mScroller.getCurrX()
    644                 || getScrollY() != mScroller.getCurrY()) {
    645                 float scaleX = mFreeScroll ? getScaleX() : 1f;
    646                 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
    647                 scrollTo(scrollX, mScroller.getCurrY());
    648             }
    649             invalidate();
    650             return true;
    651         } else if (mNextPage != INVALID_PAGE) {
    652             sendScrollAccessibilityEvent();
    653 
    654             mCurrentPage = validateNewPage(mNextPage);
    655             mNextPage = INVALID_PAGE;
    656             notifyPageSwitchListener();
    657 
    658             // We don't want to trigger a page end moving unless the page has settled
    659             // and the user has stopped scrolling
    660             if (mTouchState == TOUCH_STATE_REST) {
    661                 pageEndMoving();
    662             }
    663 
    664             onPostReorderingAnimationCompleted();
    665             AccessibilityManager am = (AccessibilityManager)
    666                     getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
    667             if (am.isEnabled()) {
    668                 // Notify the user when the page changes
    669                 announceForAccessibility(getCurrentPageDescription());
    670             }
    671             return true;
    672         }
    673         return false;
    674     }
    675 
    676     @Override
    677     public void computeScroll() {
    678         computeScrollHelper();
    679     }
    680 
    681     public static class LayoutParams extends ViewGroup.LayoutParams {
    682         public boolean isFullScreenPage = false;
    683 
    684         /**
    685          * {@inheritDoc}
    686          */
    687         public LayoutParams(int width, int height) {
    688             super(width, height);
    689         }
    690 
    691         public LayoutParams(Context context, AttributeSet attrs) {
    692             super(context, attrs);
    693         }
    694 
    695         public LayoutParams(ViewGroup.LayoutParams source) {
    696             super(source);
    697         }
    698     }
    699 
    700     @Override
    701     public LayoutParams generateLayoutParams(AttributeSet attrs) {
    702         return new LayoutParams(getContext(), attrs);
    703     }
    704 
    705     @Override
    706     protected LayoutParams generateDefaultLayoutParams() {
    707         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    708     }
    709 
    710     @Override
    711     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    712         return new LayoutParams(p);
    713     }
    714 
    715     @Override
    716     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    717         return p instanceof LayoutParams;
    718     }
    719 
    720     public void addFullScreenPage(View page) {
    721         LayoutParams lp = generateDefaultLayoutParams();
    722         lp.isFullScreenPage = true;
    723         super.addView(page, 0, lp);
    724     }
    725 
    726     public int getNormalChildHeight() {
    727         return mNormalChildHeight;
    728     }
    729 
    730     @Override
    731     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    732         if (getChildCount() == 0) {
    733             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    734             return;
    735         }
    736 
    737         // We measure the dimensions of the PagedView to be larger than the pages so that when we
    738         // zoom out (and scale down), the view is still contained in the parent
    739         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    740         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    741         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    742         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    743         // NOTE: We multiply by 2f to account for the fact that depending on the offset of the
    744         // viewport, we can be at most one and a half screens offset once we scale down
    745         DisplayMetrics dm = getResources().getDisplayMetrics();
    746         int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
    747                 dm.heightPixels + mInsets.top + mInsets.bottom);
    748 
    749         int parentWidthSize = (int) (2f * maxSize);
    750         int parentHeightSize = (int) (2f * maxSize);
    751         int scaledWidthSize, scaledHeightSize;
    752         if (mUseMinScale) {
    753             scaledWidthSize = (int) (parentWidthSize / mMinScale);
    754             scaledHeightSize = (int) (parentHeightSize / mMinScale);
    755         } else {
    756             scaledWidthSize = widthSize;
    757             scaledHeightSize = heightSize;
    758         }
    759         mViewport.set(0, 0, widthSize, heightSize);
    760 
    761         if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
    762             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    763             return;
    764         }
    765 
    766         // Return early if we aren't given a proper dimension
    767         if (widthSize <= 0 || heightSize <= 0) {
    768             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    769             return;
    770         }
    771 
    772         /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
    773          * of the All apps view on XLarge displays to not take up more space then it needs. Width
    774          * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
    775          * each page to have the same width.
    776          */
    777         final int verticalPadding = getPaddingTop() + getPaddingBottom();
    778         final int horizontalPadding = getPaddingLeft() + getPaddingRight();
    779 
    780         int referenceChildWidth = 0;
    781         // The children are given the same width and height as the workspace
    782         // unless they were set to WRAP_CONTENT
    783         if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
    784         if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
    785         if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
    786         if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
    787         if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
    788         final int childCount = getChildCount();
    789         for (int i = 0; i < childCount; i++) {
    790             // disallowing padding in paged view (just pass 0)
    791             final View child = getPageAt(i);
    792             if (child.getVisibility() != GONE) {
    793                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    794 
    795                 int childWidthMode;
    796                 int childHeightMode;
    797                 int childWidth;
    798                 int childHeight;
    799 
    800                 if (!lp.isFullScreenPage) {
    801                     if (lp.width == LayoutParams.WRAP_CONTENT) {
    802                         childWidthMode = MeasureSpec.AT_MOST;
    803                     } else {
    804                         childWidthMode = MeasureSpec.EXACTLY;
    805                     }
    806 
    807                     if (lp.height == LayoutParams.WRAP_CONTENT) {
    808                         childHeightMode = MeasureSpec.AT_MOST;
    809                     } else {
    810                         childHeightMode = MeasureSpec.EXACTLY;
    811                     }
    812 
    813                     childWidth = getViewportWidth() - horizontalPadding
    814                             - mInsets.left - mInsets.right;
    815                     childHeight = getViewportHeight() - verticalPadding
    816                             - mInsets.top - mInsets.bottom;
    817                     mNormalChildHeight = childHeight;
    818                 } else {
    819                     childWidthMode = MeasureSpec.EXACTLY;
    820                     childHeightMode = MeasureSpec.EXACTLY;
    821 
    822                     childWidth = getViewportWidth() - mInsets.left - mInsets.right;
    823                     childHeight = getViewportHeight();
    824                 }
    825                 if (referenceChildWidth == 0) {
    826                     referenceChildWidth = childWidth;
    827                 }
    828 
    829                 final int childWidthMeasureSpec =
    830                         MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
    831                     final int childHeightMeasureSpec =
    832                         MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
    833                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    834             }
    835         }
    836         setMeasuredDimension(scaledWidthSize, scaledHeightSize);
    837     }
    838 
    839     @Override
    840     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    841         if (getChildCount() == 0) {
    842             return;
    843         }
    844 
    845         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
    846         final int childCount = getChildCount();
    847 
    848         int offsetX = getViewportOffsetX();
    849         int offsetY = getViewportOffsetY();
    850 
    851         // Update the viewport offsets
    852         mViewport.offset(offsetX, offsetY);
    853 
    854         final int startIndex = mIsRtl ? childCount - 1 : 0;
    855         final int endIndex = mIsRtl ? -1 : childCount;
    856         final int delta = mIsRtl ? -1 : 1;
    857 
    858         int verticalPadding = getPaddingTop() + getPaddingBottom();
    859 
    860         LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams();
    861         LayoutParams nextLp;
    862 
    863         int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
    864         if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
    865             mPageScrolls = new int[childCount];
    866         }
    867 
    868         for (int i = startIndex; i != endIndex; i += delta) {
    869             final View child = getPageAt(i);
    870             if (child.getVisibility() != View.GONE) {
    871                 lp = (LayoutParams) child.getLayoutParams();
    872                 int childTop;
    873                 if (lp.isFullScreenPage) {
    874                     childTop = offsetY;
    875                 } else {
    876                     childTop = offsetY + getPaddingTop() + mInsets.top;
    877                     if (mCenterPagesVertically) {
    878                         childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
    879                     }
    880                 }
    881 
    882                 final int childWidth = child.getMeasuredWidth();
    883                 final int childHeight = child.getMeasuredHeight();
    884 
    885                 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
    886                 child.layout(childLeft, childTop,
    887                         childLeft + child.getMeasuredWidth(), childTop + childHeight);
    888 
    889                 int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft();
    890                 mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
    891 
    892                 int pageGap = mPageSpacing;
    893                 int next = i + delta;
    894                 if (next != endIndex) {
    895                     nextLp = (LayoutParams) getPageAt(next).getLayoutParams();
    896                 } else {
    897                     nextLp = null;
    898                 }
    899 
    900                 // Prevent full screen pages from showing in the viewport
    901                 // when they are not the current page.
    902                 if (lp.isFullScreenPage) {
    903                     pageGap = getPaddingLeft();
    904                 } else if (nextLp != null && nextLp.isFullScreenPage) {
    905                     pageGap = getPaddingRight();
    906                 }
    907 
    908                 childLeft += childWidth + pageGap + getChildGap();
    909             }
    910         }
    911 
    912         final LayoutTransition transition = getLayoutTransition();
    913         // If the transition is running defer updating max scroll, as some empty pages could
    914         // still be present, and a max scroll change could cause sudden jumps in scroll.
    915         if (transition != null && transition.isRunning()) {
    916             transition.addTransitionListener(new LayoutTransition.TransitionListener() {
    917 
    918                 @Override
    919                 public void startTransition(LayoutTransition transition, ViewGroup container,
    920                         View view, int transitionType) { }
    921 
    922                 @Override
    923                 public void endTransition(LayoutTransition transition, ViewGroup container,
    924                         View view, int transitionType) {
    925                     // Wait until all transitions are complete.
    926                     if (!transition.isRunning()) {
    927                         transition.removeTransitionListener(this);
    928                         updateMaxScrollX();
    929                     }
    930                 }
    931             });
    932         } else {
    933             updateMaxScrollX();
    934         }
    935 
    936         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < childCount) {
    937             updateCurrentPageScroll();
    938             mFirstLayout = false;
    939         }
    940 
    941         if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
    942             if (mRestorePage != INVALID_RESTORE_PAGE) {
    943                 setCurrentPage(mRestorePage);
    944                 mRestorePage = INVALID_RESTORE_PAGE;
    945             } else {
    946                 setCurrentPage(getNextPage());
    947             }
    948         }
    949         mChildCountOnLastLayout = childCount;
    950 
    951         if (isReordering(true)) {
    952             updateDragViewTranslationDuringDrag();
    953         }
    954     }
    955 
    956     protected int getChildGap() {
    957         return 0;
    958     }
    959 
    960     @Thunk void updateMaxScrollX() {
    961         int childCount = getChildCount();
    962         if (childCount > 0) {
    963             final int index = mIsRtl ? 0 : childCount - 1;
    964             mMaxScrollX = getScrollForPage(index);
    965         } else {
    966             mMaxScrollX = 0;
    967         }
    968     }
    969 
    970     public void setPageSpacing(int pageSpacing) {
    971         mPageSpacing = pageSpacing;
    972         requestLayout();
    973     }
    974 
    975     /**
    976      * Called when the center screen changes during scrolling.
    977      */
    978     protected void screenScrolled(int screenCenter) { }
    979 
    980     @Override
    981     public void onChildViewAdded(View parent, View child) {
    982         // Update the page indicator, we don't update the page indicator as we
    983         // add/remove pages
    984         if (mPageIndicator != null && !isReordering(false)) {
    985             int pageIndex = indexOfChild(child);
    986             mPageIndicator.addMarker(pageIndex,
    987                     getPageIndicatorMarker(pageIndex),
    988                     true);
    989         }
    990 
    991         // This ensures that when children are added, they get the correct transforms / alphas
    992         // in accordance with any scroll effects.
    993         mForceScreenScrolled = true;
    994         updateFreescrollBounds();
    995         invalidate();
    996     }
    997 
    998     @Override
    999     public void onChildViewRemoved(View parent, View child) {
   1000         mForceScreenScrolled = true;
   1001         updateFreescrollBounds();
   1002         invalidate();
   1003     }
   1004 
   1005     private void removeMarkerForView(int index) {
   1006         // Update the page indicator, we don't update the page indicator as we
   1007         // add/remove pages
   1008         if (mPageIndicator != null && !isReordering(false)) {
   1009             mPageIndicator.removeMarker(index, true);
   1010         }
   1011     }
   1012 
   1013     @Override
   1014     public void removeView(View v) {
   1015         // XXX: We should find a better way to hook into this before the view
   1016         // gets removed form its parent...
   1017         removeMarkerForView(indexOfChild(v));
   1018         super.removeView(v);
   1019     }
   1020     @Override
   1021     public void removeViewInLayout(View v) {
   1022         // XXX: We should find a better way to hook into this before the view
   1023         // gets removed form its parent...
   1024         removeMarkerForView(indexOfChild(v));
   1025         super.removeViewInLayout(v);
   1026     }
   1027     @Override
   1028     public void removeViewAt(int index) {
   1029         // XXX: We should find a better way to hook into this before the view
   1030         // gets removed form its parent...
   1031         removeMarkerForView(index);
   1032         super.removeViewAt(index);
   1033     }
   1034     @Override
   1035     public void removeAllViewsInLayout() {
   1036         // Update the page indicator, we don't update the page indicator as we
   1037         // add/remove pages
   1038         if (mPageIndicator != null) {
   1039             mPageIndicator.removeAllMarkers(true);
   1040         }
   1041 
   1042         super.removeAllViewsInLayout();
   1043     }
   1044 
   1045     protected int getChildOffset(int index) {
   1046         if (index < 0 || index > getChildCount() - 1) return 0;
   1047 
   1048         int offset = getPageAt(index).getLeft() - getViewportOffsetX();
   1049 
   1050         return offset;
   1051     }
   1052 
   1053     protected void getFreeScrollPageRange(int[] range) {
   1054         range[0] = 0;
   1055         range[1] = Math.max(0, getChildCount() - 1);
   1056     }
   1057 
   1058     protected void getVisiblePages(int[] range) {
   1059         final int pageCount = getChildCount();
   1060         sTmpIntPoint[0] = sTmpIntPoint[1] = 0;
   1061 
   1062         range[0] = -1;
   1063         range[1] = -1;
   1064 
   1065         if (pageCount > 0) {
   1066             int viewportWidth = getViewportWidth();
   1067             int curScreen = 0;
   1068 
   1069             int count = getChildCount();
   1070             for (int i = 0; i < count; i++) {
   1071                 View currPage = getPageAt(i);
   1072 
   1073                 sTmpIntPoint[0] = 0;
   1074                 Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false);
   1075                 if (sTmpIntPoint[0] > viewportWidth) {
   1076                     if (range[0] == -1) {
   1077                         continue;
   1078                     } else {
   1079                         break;
   1080                     }
   1081                 }
   1082 
   1083                 sTmpIntPoint[0] = currPage.getMeasuredWidth();
   1084                 Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false);
   1085                 if (sTmpIntPoint[0] < 0) {
   1086                     if (range[0] == -1) {
   1087                         continue;
   1088                     } else {
   1089                         break;
   1090                     }
   1091                 }
   1092                 curScreen = i;
   1093                 if (range[0] < 0) {
   1094                     range[0] = curScreen;
   1095                 }
   1096             }
   1097 
   1098             range[1] = curScreen;
   1099         } else {
   1100             range[0] = -1;
   1101             range[1] = -1;
   1102         }
   1103     }
   1104 
   1105     protected boolean shouldDrawChild(View child) {
   1106         return child.getVisibility() == VISIBLE;
   1107     }
   1108 
   1109     @Override
   1110     protected void dispatchDraw(Canvas canvas) {
   1111         // Find out which screens are visible; as an optimization we only call draw on them
   1112         final int pageCount = getChildCount();
   1113         if (pageCount > 0) {
   1114             int halfScreenSize = getViewportWidth() / 2;
   1115             int screenCenter = getScrollX() + halfScreenSize;
   1116 
   1117             if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
   1118                 // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
   1119                 // set it for the next frame
   1120                 mForceScreenScrolled = false;
   1121                 screenScrolled(screenCenter);
   1122                 mLastScreenCenter = screenCenter;
   1123             }
   1124 
   1125             getVisiblePages(mTempVisiblePagesRange);
   1126             final int leftScreen = mTempVisiblePagesRange[0];
   1127             final int rightScreen = mTempVisiblePagesRange[1];
   1128             if (leftScreen != -1 && rightScreen != -1) {
   1129                 final long drawingTime = getDrawingTime();
   1130                 // Clip to the bounds
   1131                 canvas.save();
   1132                 canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
   1133                         getScrollY() + getBottom() - getTop());
   1134 
   1135                 // Draw all the children, leaving the drag view for last
   1136                 for (int i = pageCount - 1; i >= 0; i--) {
   1137                     final View v = getPageAt(i);
   1138                     if (v == mDragView) continue;
   1139                     if (mForceDrawAllChildrenNextFrame ||
   1140                                (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
   1141                         drawChild(canvas, v, drawingTime);
   1142                     }
   1143                 }
   1144                 // Draw the drag view on top (if there is one)
   1145                 if (mDragView != null) {
   1146                     drawChild(canvas, mDragView, drawingTime);
   1147                 }
   1148 
   1149                 mForceDrawAllChildrenNextFrame = false;
   1150                 canvas.restore();
   1151             }
   1152         }
   1153     }
   1154 
   1155     @Override
   1156     public void draw(Canvas canvas) {
   1157         super.draw(canvas);
   1158         if (getPageCount() > 0) {
   1159             if (!mEdgeGlowLeft.isFinished()) {
   1160                 final int restoreCount = canvas.save();
   1161                 Rect display = mViewport;
   1162                 canvas.translate(display.left, display.top);
   1163                 canvas.rotate(270);
   1164 
   1165                 getEdgeVerticalPostion(sTmpIntPoint);
   1166                 canvas.translate(display.top - sTmpIntPoint[1], 0);
   1167                 mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
   1168                 if (mEdgeGlowLeft.draw(canvas)) {
   1169                     postInvalidateOnAnimation();
   1170                 }
   1171                 canvas.restoreToCount(restoreCount);
   1172             }
   1173             if (!mEdgeGlowRight.isFinished()) {
   1174                 final int restoreCount = canvas.save();
   1175                 Rect display = mViewport;
   1176                 canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top);
   1177                 canvas.rotate(90);
   1178 
   1179                 getEdgeVerticalPostion(sTmpIntPoint);
   1180                 canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
   1181                 mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
   1182                 if (mEdgeGlowRight.draw(canvas)) {
   1183                     postInvalidateOnAnimation();
   1184                 }
   1185                 canvas.restoreToCount(restoreCount);
   1186             }
   1187         }
   1188     }
   1189 
   1190     /**
   1191      * Returns the top and bottom position for the edge effect.
   1192      */
   1193     protected abstract void getEdgeVerticalPostion(int[] pos);
   1194 
   1195     @Override
   1196     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
   1197         int page = indexToPage(indexOfChild(child));
   1198         if (page != mCurrentPage || !mScroller.isFinished()) {
   1199             snapToPage(page);
   1200             return true;
   1201         }
   1202         return false;
   1203     }
   1204 
   1205     @Override
   1206     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
   1207         int focusablePage;
   1208         if (mNextPage != INVALID_PAGE) {
   1209             focusablePage = mNextPage;
   1210         } else {
   1211             focusablePage = mCurrentPage;
   1212         }
   1213         View v = getPageAt(focusablePage);
   1214         if (v != null) {
   1215             return v.requestFocus(direction, previouslyFocusedRect);
   1216         }
   1217         return false;
   1218     }
   1219 
   1220     @Override
   1221     public boolean dispatchUnhandledMove(View focused, int direction) {
   1222         // XXX-RTL: This will be fixed in a future CL
   1223         if (direction == View.FOCUS_LEFT) {
   1224             if (getCurrentPage() > 0) {
   1225                 snapToPage(getCurrentPage() - 1);
   1226                 return true;
   1227             }
   1228         } else if (direction == View.FOCUS_RIGHT) {
   1229             if (getCurrentPage() < getPageCount() - 1) {
   1230                 snapToPage(getCurrentPage() + 1);
   1231                 return true;
   1232             }
   1233         }
   1234         return super.dispatchUnhandledMove(focused, direction);
   1235     }
   1236 
   1237     @Override
   1238     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
   1239         // XXX-RTL: This will be fixed in a future CL
   1240         if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
   1241             getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
   1242         }
   1243         if (direction == View.FOCUS_LEFT) {
   1244             if (mCurrentPage > 0) {
   1245                 getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
   1246             }
   1247         } else if (direction == View.FOCUS_RIGHT){
   1248             if (mCurrentPage < getPageCount() - 1) {
   1249                 getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
   1250             }
   1251         }
   1252     }
   1253 
   1254     /**
   1255      * If one of our descendant views decides that it could be focused now, only
   1256      * pass that along if it's on the current page.
   1257      *
   1258      * This happens when live folders requery, and if they're off page, they
   1259      * end up calling requestFocus, which pulls it on page.
   1260      */
   1261     @Override
   1262     public void focusableViewAvailable(View focused) {
   1263         View current = getPageAt(mCurrentPage);
   1264         View v = focused;
   1265         while (true) {
   1266             if (v == current) {
   1267                 super.focusableViewAvailable(focused);
   1268                 return;
   1269             }
   1270             if (v == this) {
   1271                 return;
   1272             }
   1273             ViewParent parent = v.getParent();
   1274             if (parent instanceof View) {
   1275                 v = (View)v.getParent();
   1276             } else {
   1277                 return;
   1278             }
   1279         }
   1280     }
   1281 
   1282     /**
   1283      * {@inheritDoc}
   1284      */
   1285     @Override
   1286     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
   1287         if (disallowIntercept) {
   1288             // We need to make sure to cancel our long press if
   1289             // a scrollable widget takes over touch events
   1290             final View currentPage = getPageAt(mCurrentPage);
   1291             currentPage.cancelLongPress();
   1292         }
   1293         super.requestDisallowInterceptTouchEvent(disallowIntercept);
   1294     }
   1295 
   1296     /**
   1297      * Return true if a tap at (x, y) should trigger a flip to the previous page.
   1298      */
   1299     protected boolean hitsPreviousPage(float x, float y) {
   1300         if (mIsRtl) {
   1301             return (x > (getViewportOffsetX() + getViewportWidth() -
   1302                     getPaddingRight() - mPageSpacing));
   1303         }
   1304         return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
   1305     }
   1306 
   1307     /**
   1308      * Return true if a tap at (x, y) should trigger a flip to the next page.
   1309      */
   1310     protected boolean hitsNextPage(float x, float y) {
   1311         if (mIsRtl) {
   1312             return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
   1313         }
   1314         return  (x > (getViewportOffsetX() + getViewportWidth() -
   1315                 getPaddingRight() - mPageSpacing));
   1316     }
   1317 
   1318     /** Returns whether x and y originated within the buffered viewport */
   1319     private boolean isTouchPointInViewportWithBuffer(int x, int y) {
   1320         sTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
   1321                 mViewport.right + mViewport.width() / 2, mViewport.bottom);
   1322         return sTmpRect.contains(x, y);
   1323     }
   1324 
   1325     @Override
   1326     public boolean onInterceptTouchEvent(MotionEvent ev) {
   1327         /*
   1328          * This method JUST determines whether we want to intercept the motion.
   1329          * If we return true, onTouchEvent will be called and we do the actual
   1330          * scrolling there.
   1331          */
   1332         acquireVelocityTrackerAndAddMovement(ev);
   1333 
   1334         // Skip touch handling if there are no pages to swipe
   1335         if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
   1336 
   1337         /*
   1338          * Shortcut the most recurring case: the user is in the dragging
   1339          * state and he is moving his finger.  We want to intercept this
   1340          * motion.
   1341          */
   1342         final int action = ev.getAction();
   1343         if ((action == MotionEvent.ACTION_MOVE) &&
   1344                 (mTouchState == TOUCH_STATE_SCROLLING)) {
   1345             return true;
   1346         }
   1347 
   1348         switch (action & MotionEvent.ACTION_MASK) {
   1349             case MotionEvent.ACTION_MOVE: {
   1350                 /*
   1351                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
   1352                  * whether the user has moved far enough from his original down touch.
   1353                  */
   1354                 if (mActivePointerId != INVALID_POINTER) {
   1355                     determineScrollingStart(ev);
   1356                 }
   1357                 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
   1358                 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
   1359                 // i.e. fall through to the next case (don't break)
   1360                 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
   1361                 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
   1362                 break;
   1363             }
   1364 
   1365             case MotionEvent.ACTION_DOWN: {
   1366                 final float x = ev.getX();
   1367                 final float y = ev.getY();
   1368                 // Remember location of down touch
   1369                 mDownMotionX = x;
   1370                 mDownMotionY = y;
   1371                 mDownScrollX = getScrollX();
   1372                 mLastMotionX = x;
   1373                 mLastMotionY = y;
   1374                 float[] p = mapPointFromViewToParent(this, x, y);
   1375                 mParentDownMotionX = p[0];
   1376                 mParentDownMotionY = p[1];
   1377                 mLastMotionXRemainder = 0;
   1378                 mTotalMotionX = 0;
   1379                 mActivePointerId = ev.getPointerId(0);
   1380 
   1381                 /*
   1382                  * If being flinged and user touches the screen, initiate drag;
   1383                  * otherwise don't.  mScroller.isFinished should be false when
   1384                  * being flinged.
   1385                  */
   1386                 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
   1387                 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
   1388 
   1389                 if (finishedScrolling) {
   1390                     mTouchState = TOUCH_STATE_REST;
   1391                     if (!mScroller.isFinished() && !mFreeScroll) {
   1392                         setCurrentPage(getNextPage());
   1393                         pageEndMoving();
   1394                     }
   1395                 } else {
   1396                     if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
   1397                         mTouchState = TOUCH_STATE_SCROLLING;
   1398                     } else {
   1399                         mTouchState = TOUCH_STATE_REST;
   1400                     }
   1401                 }
   1402 
   1403                 break;
   1404             }
   1405 
   1406             case MotionEvent.ACTION_UP:
   1407             case MotionEvent.ACTION_CANCEL:
   1408                 resetTouchState();
   1409                 break;
   1410 
   1411             case MotionEvent.ACTION_POINTER_UP:
   1412                 onSecondaryPointerUp(ev);
   1413                 releaseVelocityTracker();
   1414                 break;
   1415         }
   1416 
   1417         /*
   1418          * The only time we want to intercept motion events is if we are in the
   1419          * drag mode.
   1420          */
   1421         return mTouchState != TOUCH_STATE_REST;
   1422     }
   1423 
   1424     protected void determineScrollingStart(MotionEvent ev) {
   1425         determineScrollingStart(ev, 1.0f);
   1426     }
   1427 
   1428     /*
   1429      * Determines if we should change the touch state to start scrolling after the
   1430      * user moves their touch point too far.
   1431      */
   1432     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
   1433         // Disallow scrolling if we don't have a valid pointer index
   1434         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
   1435         if (pointerIndex == -1) return;
   1436 
   1437         // Disallow scrolling if we started the gesture from outside the viewport
   1438         final float x = ev.getX(pointerIndex);
   1439         final float y = ev.getY(pointerIndex);
   1440         if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
   1441 
   1442         final int xDiff = (int) Math.abs(x - mLastMotionX);
   1443 
   1444         final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
   1445         boolean xMoved = xDiff > touchSlop;
   1446 
   1447         if (xMoved) {
   1448             // Scroll if the user moved far enough along the X axis
   1449             mTouchState = TOUCH_STATE_SCROLLING;
   1450             mTotalMotionX += Math.abs(mLastMotionX - x);
   1451             mLastMotionX = x;
   1452             mLastMotionXRemainder = 0;
   1453             mTouchX = getViewportOffsetX() + getScrollX();
   1454             mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
   1455             onScrollInteractionBegin();
   1456             pageBeginMoving();
   1457         }
   1458     }
   1459 
   1460     protected void cancelCurrentPageLongPress() {
   1461         // Try canceling the long press. It could also have been scheduled
   1462         // by a distant descendant, so use the mAllowLongPress flag to block
   1463         // everything
   1464         final View currentPage = getPageAt(mCurrentPage);
   1465         if (currentPage != null) {
   1466             currentPage.cancelLongPress();
   1467         }
   1468     }
   1469 
   1470     protected float getScrollProgress(int screenCenter, View v, int page) {
   1471         final int halfScreenSize = getViewportWidth() / 2;
   1472 
   1473         int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
   1474         int count = getChildCount();
   1475 
   1476         final int totalDistance;
   1477 
   1478         int adjacentPage = page + 1;
   1479         if ((delta < 0 && !mIsRtl) || (delta > 0 && mIsRtl)) {
   1480             adjacentPage = page - 1;
   1481         }
   1482 
   1483         if (adjacentPage < 0 || adjacentPage > count - 1) {
   1484             totalDistance = v.getMeasuredWidth() + mPageSpacing;
   1485         } else {
   1486             totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page));
   1487         }
   1488 
   1489         float scrollProgress = delta / (totalDistance * 1.0f);
   1490         scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS);
   1491         scrollProgress = Math.max(scrollProgress, - MAX_SCROLL_PROGRESS);
   1492         return scrollProgress;
   1493     }
   1494 
   1495     public int getScrollForPage(int index) {
   1496         if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
   1497             return 0;
   1498         } else {
   1499             return mPageScrolls[index];
   1500         }
   1501     }
   1502 
   1503     // While layout transitions are occurring, a child's position may stray from its baseline
   1504     // position. This method returns the magnitude of this stray at any given time.
   1505     public int getLayoutTransitionOffsetForPage(int index) {
   1506         if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
   1507             return 0;
   1508         } else {
   1509             View child = getChildAt(index);
   1510 
   1511             int scrollOffset = 0;
   1512             LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1513             if (!lp.isFullScreenPage) {
   1514                 scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
   1515             }
   1516 
   1517             int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
   1518             return (int) (child.getX() - baselineX);
   1519         }
   1520     }
   1521 
   1522     protected void dampedOverScroll(float amount) {
   1523         int screenSize = getViewportWidth();
   1524         float f = (amount / screenSize);
   1525         if (f < 0) {
   1526             mEdgeGlowLeft.onPull(-f);
   1527         } else if (f > 0) {
   1528             mEdgeGlowRight.onPull(f);
   1529         } else {
   1530             return;
   1531         }
   1532         invalidate();
   1533     }
   1534 
   1535     protected void overScroll(float amount) {
   1536         dampedOverScroll(amount);
   1537     }
   1538 
   1539     public void enableFreeScroll() {
   1540         setEnableFreeScroll(true);
   1541     }
   1542 
   1543     public void disableFreeScroll() {
   1544         setEnableFreeScroll(false);
   1545     }
   1546 
   1547     void updateFreescrollBounds() {
   1548         getFreeScrollPageRange(mTempVisiblePagesRange);
   1549         if (mIsRtl) {
   1550             mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
   1551             mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
   1552         } else {
   1553             mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
   1554             mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
   1555         }
   1556     }
   1557 
   1558     private void setEnableFreeScroll(boolean freeScroll) {
   1559         mFreeScroll = freeScroll;
   1560 
   1561         if (mFreeScroll) {
   1562             updateFreescrollBounds();
   1563             getFreeScrollPageRange(mTempVisiblePagesRange);
   1564             if (getCurrentPage() < mTempVisiblePagesRange[0]) {
   1565                 setCurrentPage(mTempVisiblePagesRange[0]);
   1566             } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
   1567                 setCurrentPage(mTempVisiblePagesRange[1]);
   1568             }
   1569         }
   1570 
   1571         setEnableOverscroll(!freeScroll);
   1572     }
   1573 
   1574     protected void setEnableOverscroll(boolean enable) {
   1575         mAllowOverScroll = enable;
   1576     }
   1577 
   1578     private int getNearestHoverOverPageIndex() {
   1579         if (mDragView != null) {
   1580             int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
   1581                     + mDragView.getTranslationX());
   1582             getFreeScrollPageRange(mTempVisiblePagesRange);
   1583             int minDistance = Integer.MAX_VALUE;
   1584             int minIndex = indexOfChild(mDragView);
   1585             for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
   1586                 View page = getPageAt(i);
   1587                 int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
   1588                 int d = Math.abs(dragX - pageX);
   1589                 if (d < minDistance) {
   1590                     minIndex = i;
   1591                     minDistance = d;
   1592                 }
   1593             }
   1594             return minIndex;
   1595         }
   1596         return -1;
   1597     }
   1598 
   1599     @Override
   1600     public boolean onTouchEvent(MotionEvent ev) {
   1601         super.onTouchEvent(ev);
   1602 
   1603         // Skip touch handling if there are no pages to swipe
   1604         if (getChildCount() <= 0) return super.onTouchEvent(ev);
   1605 
   1606         acquireVelocityTrackerAndAddMovement(ev);
   1607 
   1608         final int action = ev.getAction();
   1609 
   1610         switch (action & MotionEvent.ACTION_MASK) {
   1611         case MotionEvent.ACTION_DOWN:
   1612             /*
   1613              * If being flinged and user touches, stop the fling. isFinished
   1614              * will be false if being flinged.
   1615              */
   1616             if (!mScroller.isFinished()) {
   1617                 abortScrollerAnimation(false);
   1618             }
   1619 
   1620             // Remember where the motion event started
   1621             mDownMotionX = mLastMotionX = ev.getX();
   1622             mDownMotionY = mLastMotionY = ev.getY();
   1623             mDownScrollX = getScrollX();
   1624             float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
   1625             mParentDownMotionX = p[0];
   1626             mParentDownMotionY = p[1];
   1627             mLastMotionXRemainder = 0;
   1628             mTotalMotionX = 0;
   1629             mActivePointerId = ev.getPointerId(0);
   1630 
   1631             if (mTouchState == TOUCH_STATE_SCROLLING) {
   1632                 onScrollInteractionBegin();
   1633                 pageBeginMoving();
   1634             }
   1635             break;
   1636 
   1637         case MotionEvent.ACTION_MOVE:
   1638             if (mTouchState == TOUCH_STATE_SCROLLING) {
   1639                 // Scroll to follow the motion event
   1640                 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
   1641 
   1642                 if (pointerIndex == -1) return true;
   1643 
   1644                 final float x = ev.getX(pointerIndex);
   1645                 final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
   1646 
   1647                 mTotalMotionX += Math.abs(deltaX);
   1648 
   1649                 // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
   1650                 // keep the remainder because we are actually testing if we've moved from the last
   1651                 // scrolled position (which is discrete).
   1652                 if (Math.abs(deltaX) >= 1.0f) {
   1653                     mTouchX += deltaX;
   1654                     mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
   1655                     scrollBy((int) deltaX, 0);
   1656                     mLastMotionX = x;
   1657                     mLastMotionXRemainder = deltaX - (int) deltaX;
   1658                 } else {
   1659                     awakenScrollBars();
   1660                 }
   1661             } else if (mTouchState == TOUCH_STATE_REORDERING) {
   1662                 // Update the last motion position
   1663                 mLastMotionX = ev.getX();
   1664                 mLastMotionY = ev.getY();
   1665 
   1666                 // Update the parent down so that our zoom animations take this new movement into
   1667                 // account
   1668                 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
   1669                 mParentDownMotionX = pt[0];
   1670                 mParentDownMotionY = pt[1];
   1671                 updateDragViewTranslationDuringDrag();
   1672 
   1673                 // Find the closest page to the touch point
   1674                 final int dragViewIndex = indexOfChild(mDragView);
   1675 
   1676                 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
   1677                 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
   1678                 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
   1679                 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
   1680 
   1681                 final int pageUnderPointIndex = getNearestHoverOverPageIndex();
   1682                 if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView)) {
   1683                     mTempVisiblePagesRange[0] = 0;
   1684                     mTempVisiblePagesRange[1] = getPageCount() - 1;
   1685                     getFreeScrollPageRange(mTempVisiblePagesRange);
   1686                     if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
   1687                             pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
   1688                             pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
   1689                         mSidePageHoverIndex = pageUnderPointIndex;
   1690                         mSidePageHoverRunnable = new Runnable() {
   1691                             @Override
   1692                             public void run() {
   1693                                 // Setup the scroll to the correct page before we swap the views
   1694                                 snapToPage(pageUnderPointIndex);
   1695 
   1696                                 // For each of the pages between the paged view and the drag view,
   1697                                 // animate them from the previous position to the new position in
   1698                                 // the layout (as a result of the drag view moving in the layout)
   1699                                 int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
   1700                                 int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
   1701                                         dragViewIndex + 1 : pageUnderPointIndex;
   1702                                 int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
   1703                                         dragViewIndex - 1 : pageUnderPointIndex;
   1704                                 for (int i = lowerIndex; i <= upperIndex; ++i) {
   1705                                     View v = getChildAt(i);
   1706                                     // dragViewIndex < pageUnderPointIndex, so after we remove the
   1707                                     // drag view all subsequent views to pageUnderPointIndex will
   1708                                     // shift down.
   1709                                     int oldX = getViewportOffsetX() + getChildOffset(i);
   1710                                     int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
   1711 
   1712                                     // Animate the view translation from its old position to its new
   1713                                     // position
   1714                                     AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY);
   1715                                     if (anim != null) {
   1716                                         anim.cancel();
   1717                                     }
   1718 
   1719                                     v.setTranslationX(oldX - newX);
   1720                                     anim = new AnimatorSet();
   1721                                     anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
   1722                                     anim.playTogether(
   1723                                             ObjectAnimator.ofFloat(v, "translationX", 0f));
   1724                                     anim.start();
   1725                                     v.setTag(anim);
   1726                                 }
   1727 
   1728                                 removeView(mDragView);
   1729                                 addView(mDragView, pageUnderPointIndex);
   1730                                 mSidePageHoverIndex = -1;
   1731                                 if (mPageIndicator != null) {
   1732                                     mPageIndicator.setActiveMarker(getNextPage());
   1733                                 }
   1734                             }
   1735                         };
   1736                         postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
   1737                     }
   1738                 } else {
   1739                     removeCallbacks(mSidePageHoverRunnable);
   1740                     mSidePageHoverIndex = -1;
   1741                 }
   1742             } else {
   1743                 determineScrollingStart(ev);
   1744             }
   1745             break;
   1746 
   1747         case MotionEvent.ACTION_UP:
   1748             if (mTouchState == TOUCH_STATE_SCROLLING) {
   1749                 final int activePointerId = mActivePointerId;
   1750                 final int pointerIndex = ev.findPointerIndex(activePointerId);
   1751                 final float x = ev.getX(pointerIndex);
   1752                 final VelocityTracker velocityTracker = mVelocityTracker;
   1753                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
   1754                 int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
   1755                 final int deltaX = (int) (x - mDownMotionX);
   1756                 final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
   1757                 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
   1758                         SIGNIFICANT_MOVE_THRESHOLD;
   1759 
   1760                 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
   1761 
   1762                 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
   1763                         Math.abs(velocityX) > mFlingThresholdVelocity;
   1764 
   1765                 if (!mFreeScroll) {
   1766                     // In the case that the page is moved far to one direction and then is flung
   1767                     // in the opposite direction, we use a threshold to determine whether we should
   1768                     // just return to the starting page, or if we should skip one further.
   1769                     boolean returnToOriginalPage = false;
   1770                     if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
   1771                             Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
   1772                         returnToOriginalPage = true;
   1773                     }
   1774 
   1775                     int finalPage;
   1776                     // We give flings precedence over large moves, which is why we short-circuit our
   1777                     // test for a large move if a fling has been registered. That is, a large
   1778                     // move to the left and fling to the right will register as a fling to the right.
   1779                     boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
   1780                     boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
   1781                     if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
   1782                             (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
   1783                         finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
   1784                         snapToPageWithVelocity(finalPage, velocityX);
   1785                     } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
   1786                             (isFling && isVelocityXLeft)) &&
   1787                             mCurrentPage < getChildCount() - 1) {
   1788                         finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
   1789                         snapToPageWithVelocity(finalPage, velocityX);
   1790                     } else {
   1791                         snapToDestination();
   1792                     }
   1793                 } else {
   1794                     if (!mScroller.isFinished()) {
   1795                         abortScrollerAnimation(true);
   1796                     }
   1797 
   1798                     float scaleX = getScaleX();
   1799                     int vX = (int) (-velocityX * scaleX);
   1800                     int initialScrollX = (int) (getScrollX() * scaleX);
   1801 
   1802                     mScroller.setInterpolator(mDefaultInterpolator);
   1803                     mScroller.fling(initialScrollX,
   1804                             getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
   1805                     invalidate();
   1806                 }
   1807                 onScrollInteractionEnd();
   1808             } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
   1809                 // at this point we have not moved beyond the touch slop
   1810                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
   1811                 // we can just page
   1812                 int nextPage = Math.max(0, mCurrentPage - 1);
   1813                 if (nextPage != mCurrentPage) {
   1814                     snapToPage(nextPage);
   1815                 } else {
   1816                     snapToDestination();
   1817                 }
   1818             } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
   1819                 // at this point we have not moved beyond the touch slop
   1820                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
   1821                 // we can just page
   1822                 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
   1823                 if (nextPage != mCurrentPage) {
   1824                     snapToPage(nextPage);
   1825                 } else {
   1826                     snapToDestination();
   1827                 }
   1828             } else if (mTouchState == TOUCH_STATE_REORDERING) {
   1829                 // Update the last motion position
   1830                 mLastMotionX = ev.getX();
   1831                 mLastMotionY = ev.getY();
   1832 
   1833                 // Update the parent down so that our zoom animations take this new movement into
   1834                 // account
   1835                 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
   1836                 mParentDownMotionX = pt[0];
   1837                 mParentDownMotionY = pt[1];
   1838                 updateDragViewTranslationDuringDrag();
   1839             } else {
   1840                 if (!mCancelTap) {
   1841                     onUnhandledTap(ev);
   1842                 }
   1843             }
   1844 
   1845             // Remove the callback to wait for the side page hover timeout
   1846             removeCallbacks(mSidePageHoverRunnable);
   1847             // End any intermediate reordering states
   1848             resetTouchState();
   1849             break;
   1850 
   1851         case MotionEvent.ACTION_CANCEL:
   1852             if (mTouchState == TOUCH_STATE_SCROLLING) {
   1853                 snapToDestination();
   1854             }
   1855             resetTouchState();
   1856             break;
   1857 
   1858         case MotionEvent.ACTION_POINTER_UP:
   1859             onSecondaryPointerUp(ev);
   1860             releaseVelocityTracker();
   1861             break;
   1862         }
   1863 
   1864         return true;
   1865     }
   1866 
   1867     private void resetTouchState() {
   1868         releaseVelocityTracker();
   1869         endReordering();
   1870         mCancelTap = false;
   1871         mTouchState = TOUCH_STATE_REST;
   1872         mActivePointerId = INVALID_POINTER;
   1873         mEdgeGlowLeft.onRelease();
   1874         mEdgeGlowRight.onRelease();
   1875     }
   1876 
   1877     /**
   1878      * Triggered by scrolling via touch
   1879      */
   1880     protected void onScrollInteractionBegin() {
   1881     }
   1882 
   1883     protected void onScrollInteractionEnd() {
   1884     }
   1885 
   1886     protected void onUnhandledTap(MotionEvent ev) {
   1887         ((Launcher) getContext()).onClick(this);
   1888     }
   1889 
   1890     @Override
   1891     public boolean onGenericMotionEvent(MotionEvent event) {
   1892         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
   1893             switch (event.getAction()) {
   1894                 case MotionEvent.ACTION_SCROLL: {
   1895                     // Handle mouse (or ext. device) by shifting the page depending on the scroll
   1896                     final float vscroll;
   1897                     final float hscroll;
   1898                     if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
   1899                         vscroll = 0;
   1900                         hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
   1901                     } else {
   1902                         vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
   1903                         hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
   1904                     }
   1905                     if (hscroll != 0 || vscroll != 0) {
   1906                         boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0)
   1907                                                          : (hscroll > 0 || vscroll > 0);
   1908                         if (isForwardScroll) {
   1909                             scrollRight();
   1910                         } else {
   1911                             scrollLeft();
   1912                         }
   1913                         return true;
   1914                     }
   1915                 }
   1916             }
   1917         }
   1918         return super.onGenericMotionEvent(event);
   1919     }
   1920 
   1921     private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
   1922         if (mVelocityTracker == null) {
   1923             mVelocityTracker = VelocityTracker.obtain();
   1924         }
   1925         mVelocityTracker.addMovement(ev);
   1926     }
   1927 
   1928     private void releaseVelocityTracker() {
   1929         if (mVelocityTracker != null) {
   1930             mVelocityTracker.clear();
   1931             mVelocityTracker.recycle();
   1932             mVelocityTracker = null;
   1933         }
   1934     }
   1935 
   1936     private void onSecondaryPointerUp(MotionEvent ev) {
   1937         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
   1938                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
   1939         final int pointerId = ev.getPointerId(pointerIndex);
   1940         if (pointerId == mActivePointerId) {
   1941             // This was our active pointer going up. Choose a new
   1942             // active pointer and adjust accordingly.
   1943             // TODO: Make this decision more intelligent.
   1944             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
   1945             mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
   1946             mLastMotionY = ev.getY(newPointerIndex);
   1947             mLastMotionXRemainder = 0;
   1948             mActivePointerId = ev.getPointerId(newPointerIndex);
   1949             if (mVelocityTracker != null) {
   1950                 mVelocityTracker.clear();
   1951             }
   1952         }
   1953     }
   1954 
   1955     @Override
   1956     public void requestChildFocus(View child, View focused) {
   1957         super.requestChildFocus(child, focused);
   1958         int page = indexToPage(indexOfChild(child));
   1959         if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
   1960             snapToPage(page);
   1961         }
   1962     }
   1963 
   1964     int getPageNearestToCenterOfScreen() {
   1965         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
   1966         int minDistanceFromScreenCenterIndex = -1;
   1967         int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
   1968         final int childCount = getChildCount();
   1969         for (int i = 0; i < childCount; ++i) {
   1970             View layout = (View) getPageAt(i);
   1971             int childWidth = layout.getMeasuredWidth();
   1972             int halfChildWidth = (childWidth / 2);
   1973             int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
   1974             int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
   1975             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
   1976                 minDistanceFromScreenCenter = distanceFromScreenCenter;
   1977                 minDistanceFromScreenCenterIndex = i;
   1978             }
   1979         }
   1980         return minDistanceFromScreenCenterIndex;
   1981     }
   1982 
   1983     protected void snapToDestination() {
   1984         snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
   1985     }
   1986 
   1987     private static class ScrollInterpolator implements Interpolator {
   1988         public ScrollInterpolator() {
   1989         }
   1990 
   1991         public float getInterpolation(float t) {
   1992             t -= 1.0f;
   1993             return t*t*t*t*t + 1;
   1994         }
   1995     }
   1996 
   1997     // We want the duration of the page snap animation to be influenced by the distance that
   1998     // the screen has to travel, however, we don't want this duration to be effected in a
   1999     // purely linear fashion. Instead, we use this method to moderate the effect that the distance
   2000     // of travel has on the overall snap duration.
   2001     private float distanceInfluenceForSnapDuration(float f) {
   2002         f -= 0.5f; // center the values about 0.
   2003         f *= 0.3f * Math.PI / 2.0f;
   2004         return (float) Math.sin(f);
   2005     }
   2006 
   2007     protected void snapToPageWithVelocity(int whichPage, int velocity) {
   2008         whichPage = validateNewPage(whichPage);
   2009         int halfScreenSize = getViewportWidth() / 2;
   2010 
   2011         final int newX = getScrollForPage(whichPage);
   2012         int delta = newX - getScrollX();
   2013         int duration = 0;
   2014 
   2015         if (Math.abs(velocity) < mMinFlingVelocity) {
   2016             // If the velocity is low enough, then treat this more as an automatic page advance
   2017             // as opposed to an apparent physical response to flinging
   2018             snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
   2019             return;
   2020         }
   2021 
   2022         // Here we compute a "distance" that will be used in the computation of the overall
   2023         // snap duration. This is a function of the actual distance that needs to be traveled;
   2024         // we keep this value close to half screen size in order to reduce the variance in snap
   2025         // duration as a function of the distance the page needs to travel.
   2026         float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
   2027         float distance = halfScreenSize + halfScreenSize *
   2028                 distanceInfluenceForSnapDuration(distanceRatio);
   2029 
   2030         velocity = Math.abs(velocity);
   2031         velocity = Math.max(mMinSnapVelocity, velocity);
   2032 
   2033         // we want the page's snap velocity to approximately match the velocity at which the
   2034         // user flings, so we scale the duration by a value near to the derivative of the scroll
   2035         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
   2036         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
   2037 
   2038         snapToPage(whichPage, delta, duration);
   2039     }
   2040 
   2041     public void snapToPage(int whichPage) {
   2042         snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
   2043     }
   2044 
   2045     protected void snapToPageImmediately(int whichPage) {
   2046         snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
   2047     }
   2048 
   2049     protected void snapToPage(int whichPage, int duration) {
   2050         snapToPage(whichPage, duration, false, null);
   2051     }
   2052 
   2053     protected void snapToPage(int whichPage, int duration, TimeInterpolator interpolator) {
   2054         snapToPage(whichPage, duration, false, interpolator);
   2055     }
   2056 
   2057     protected void snapToPage(int whichPage, int duration, boolean immediate,
   2058             TimeInterpolator interpolator) {
   2059         whichPage = validateNewPage(whichPage);
   2060 
   2061         int newX = getScrollForPage(whichPage);
   2062         final int delta = newX - getScrollX();
   2063         snapToPage(whichPage, delta, duration, immediate, interpolator);
   2064     }
   2065 
   2066     protected void snapToPage(int whichPage, int delta, int duration) {
   2067         snapToPage(whichPage, delta, duration, false, null);
   2068     }
   2069 
   2070     protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,
   2071             TimeInterpolator interpolator) {
   2072         whichPage = validateNewPage(whichPage);
   2073 
   2074         mNextPage = whichPage;
   2075         View focusedChild = getFocusedChild();
   2076         if (focusedChild != null && whichPage != mCurrentPage &&
   2077                 focusedChild == getPageAt(mCurrentPage)) {
   2078             focusedChild.clearFocus();
   2079         }
   2080 
   2081         pageBeginMoving();
   2082         awakenScrollBars(duration);
   2083         if (immediate) {
   2084             duration = 0;
   2085         } else if (duration == 0) {
   2086             duration = Math.abs(delta);
   2087         }
   2088 
   2089         if (!mScroller.isFinished()) {
   2090             abortScrollerAnimation(false);
   2091         }
   2092 
   2093         if (interpolator != null) {
   2094             mScroller.setInterpolator(interpolator);
   2095         } else {
   2096             mScroller.setInterpolator(mDefaultInterpolator);
   2097         }
   2098 
   2099         mScroller.startScroll(getScrollX(), 0, delta, 0, duration);
   2100 
   2101         updatePageIndicator();
   2102 
   2103         // Trigger a compute() to finish switching pages if necessary
   2104         if (immediate) {
   2105             computeScroll();
   2106         }
   2107 
   2108         mForceScreenScrolled = true;
   2109         invalidate();
   2110     }
   2111 
   2112     public void scrollLeft() {
   2113         if (getNextPage() > 0) snapToPage(getNextPage() - 1);
   2114     }
   2115 
   2116     public void scrollRight() {
   2117         if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
   2118     }
   2119 
   2120     public int getPageForView(View v) {
   2121         int result = -1;
   2122         if (v != null) {
   2123             ViewParent vp = v.getParent();
   2124             int count = getChildCount();
   2125             for (int i = 0; i < count; i++) {
   2126                 if (vp == getPageAt(i)) {
   2127                     return i;
   2128                 }
   2129             }
   2130         }
   2131         return result;
   2132     }
   2133 
   2134     @Override
   2135     public boolean performLongClick() {
   2136         mCancelTap = true;
   2137         return super.performLongClick();
   2138     }
   2139 
   2140     public static class SavedState extends BaseSavedState {
   2141         int currentPage = -1;
   2142 
   2143         SavedState(Parcelable superState) {
   2144             super(superState);
   2145         }
   2146 
   2147         @Thunk SavedState(Parcel in) {
   2148             super(in);
   2149             currentPage = in.readInt();
   2150         }
   2151 
   2152         @Override
   2153         public void writeToParcel(Parcel out, int flags) {
   2154             super.writeToParcel(out, flags);
   2155             out.writeInt(currentPage);
   2156         }
   2157 
   2158         public static final Parcelable.Creator<SavedState> CREATOR =
   2159                 new Parcelable.Creator<SavedState>() {
   2160             public SavedState createFromParcel(Parcel in) {
   2161                 return new SavedState(in);
   2162             }
   2163 
   2164             public SavedState[] newArray(int size) {
   2165                 return new SavedState[size];
   2166             }
   2167         };
   2168     }
   2169 
   2170     // Animate the drag view back to the original position
   2171     private void animateDragViewToOriginalPosition() {
   2172         if (mDragView != null) {
   2173             AnimatorSet anim = new AnimatorSet();
   2174             anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
   2175             anim.playTogether(
   2176                     ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
   2177                     ObjectAnimator.ofFloat(mDragView, "translationY", 0f),
   2178                     ObjectAnimator.ofFloat(mDragView, "scaleX", 1f),
   2179                     ObjectAnimator.ofFloat(mDragView, "scaleY", 1f));
   2180             anim.addListener(new AnimatorListenerAdapter() {
   2181                 @Override
   2182                 public void onAnimationEnd(Animator animation) {
   2183                     onPostReorderingAnimationCompleted();
   2184                 }
   2185             });
   2186             anim.start();
   2187         }
   2188     }
   2189 
   2190     public void onStartReordering() {
   2191         // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
   2192         mTouchState = TOUCH_STATE_REORDERING;
   2193         mIsReordering = true;
   2194 
   2195         // We must invalidate to trigger a redraw to update the layers such that the drag view
   2196         // is always drawn on top
   2197         invalidate();
   2198     }
   2199 
   2200     @Thunk void onPostReorderingAnimationCompleted() {
   2201         // Trigger the callback when reordering has settled
   2202         --mPostReorderingPreZoomInRemainingAnimationCount;
   2203         if (mPostReorderingPreZoomInRunnable != null &&
   2204                 mPostReorderingPreZoomInRemainingAnimationCount == 0) {
   2205             mPostReorderingPreZoomInRunnable.run();
   2206             mPostReorderingPreZoomInRunnable = null;
   2207         }
   2208     }
   2209 
   2210     public void onEndReordering() {
   2211         mIsReordering = false;
   2212     }
   2213 
   2214     public boolean startReordering(View v) {
   2215         int dragViewIndex = indexOfChild(v);
   2216 
   2217         if (mTouchState != TOUCH_STATE_REST || dragViewIndex == -1) return false;
   2218 
   2219         mTempVisiblePagesRange[0] = 0;
   2220         mTempVisiblePagesRange[1] = getPageCount() - 1;
   2221         getFreeScrollPageRange(mTempVisiblePagesRange);
   2222         mReorderingStarted = true;
   2223 
   2224         // Check if we are within the reordering range
   2225         if (mTempVisiblePagesRange[0] <= dragViewIndex &&
   2226             dragViewIndex <= mTempVisiblePagesRange[1]) {
   2227             // Find the drag view under the pointer
   2228             mDragView = getChildAt(dragViewIndex);
   2229             mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
   2230             mDragViewBaselineLeft = mDragView.getLeft();
   2231             snapToPage(getPageNearestToCenterOfScreen());
   2232             disableFreeScroll();
   2233             onStartReordering();
   2234             return true;
   2235         }
   2236         return false;
   2237     }
   2238 
   2239     boolean isReordering(boolean testTouchState) {
   2240         boolean state = mIsReordering;
   2241         if (testTouchState) {
   2242             state &= (mTouchState == TOUCH_STATE_REORDERING);
   2243         }
   2244         return state;
   2245     }
   2246     void endReordering() {
   2247         // For simplicity, we call endReordering sometimes even if reordering was never started.
   2248         // In that case, we don't want to do anything.
   2249         if (!mReorderingStarted) return;
   2250         mReorderingStarted = false;
   2251 
   2252         // If we haven't flung-to-delete the current child, then we just animate the drag view
   2253         // back into position
   2254         final Runnable onCompleteRunnable = new Runnable() {
   2255             @Override
   2256             public void run() {
   2257                 onEndReordering();
   2258             }
   2259         };
   2260 
   2261         mPostReorderingPreZoomInRunnable = new Runnable() {
   2262             public void run() {
   2263                 onCompleteRunnable.run();
   2264                 enableFreeScroll();
   2265             };
   2266         };
   2267 
   2268         mPostReorderingPreZoomInRemainingAnimationCount =
   2269                 NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
   2270         // Snap to the current page
   2271         snapToPage(indexOfChild(mDragView), 0);
   2272         // Animate the drag view back to the front position
   2273         animateDragViewToOriginalPosition();
   2274     }
   2275 
   2276     private static final int ANIM_TAG_KEY = 100;
   2277 
   2278     /* Accessibility */
   2279     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
   2280     @Override
   2281     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
   2282         super.onInitializeAccessibilityNodeInfo(info);
   2283         info.setScrollable(getPageCount() > 1);
   2284         if (getCurrentPage() < getPageCount() - 1) {
   2285             info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
   2286         }
   2287         if (getCurrentPage() > 0) {
   2288             info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
   2289         }
   2290         info.setClassName(getClass().getName());
   2291 
   2292         // Accessibility-wise, PagedView doesn't support long click, so disabling it.
   2293         // Besides disabling the accessibility long-click, this also prevents this view from getting
   2294         // accessibility focus.
   2295         info.setLongClickable(false);
   2296         if (Utilities.isLmpOrAbove()) {
   2297             info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
   2298         }
   2299     }
   2300 
   2301     @Override
   2302     public void sendAccessibilityEvent(int eventType) {
   2303         // Don't let the view send real scroll events.
   2304         if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
   2305             super.sendAccessibilityEvent(eventType);
   2306         }
   2307     }
   2308 
   2309     @Override
   2310     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
   2311         super.onInitializeAccessibilityEvent(event);
   2312         event.setScrollable(getPageCount() > 1);
   2313     }
   2314 
   2315     @Override
   2316     public boolean performAccessibilityAction(int action, Bundle arguments) {
   2317         if (super.performAccessibilityAction(action, arguments)) {
   2318             return true;
   2319         }
   2320         switch (action) {
   2321             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
   2322                 if (getCurrentPage() < getPageCount() - 1) {
   2323                     scrollRight();
   2324                     return true;
   2325                 }
   2326             } break;
   2327             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
   2328                 if (getCurrentPage() > 0) {
   2329                     scrollLeft();
   2330                     return true;
   2331                 }
   2332             } break;
   2333         }
   2334         return false;
   2335     }
   2336 
   2337     protected String getCurrentPageDescription() {
   2338         return String.format(getContext().getString(R.string.default_scroll_format),
   2339                 getNextPage() + 1, getChildCount());
   2340     }
   2341 
   2342     @Override
   2343     public boolean onHoverEvent(android.view.MotionEvent event) {
   2344         return true;
   2345     }
   2346 }
   2347