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