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