PagedView.java revision 96d30a165185dc59617e181314f4d22634e22952
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.ObjectAnimator;
23import android.animation.TimeInterpolator;
24import android.animation.ValueAnimator;
25import android.animation.ValueAnimator.AnimatorUpdateListener;
26import android.content.Context;
27import android.content.res.Resources;
28import android.content.res.TypedArray;
29import android.graphics.Canvas;
30import android.graphics.Matrix;
31import android.graphics.PointF;
32import android.graphics.Rect;
33import android.os.Bundle;
34import android.os.Parcel;
35import android.os.Parcelable;
36import android.util.AttributeSet;
37import android.util.DisplayMetrics;
38import android.util.Log;
39import android.view.InputDevice;
40import android.view.KeyEvent;
41import android.view.MotionEvent;
42import android.view.VelocityTracker;
43import android.view.View;
44import android.view.ViewConfiguration;
45import android.view.ViewGroup;
46import android.view.ViewParent;
47import android.view.ViewGroup.LayoutParams;
48import android.view.accessibility.AccessibilityEvent;
49import android.view.accessibility.AccessibilityManager;
50import android.view.accessibility.AccessibilityNodeInfo;
51import android.view.animation.AnimationUtils;
52import android.view.animation.DecelerateInterpolator;
53import android.view.animation.Interpolator;
54import android.view.animation.LinearInterpolator;
55import android.widget.FrameLayout;
56import android.widget.Scroller;
57
58import java.util.ArrayList;
59
60/**
61 * An abstraction of the original Workspace which supports browsing through a
62 * sequential list of "pages"
63 */
64public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
65    private static final String TAG = "PagedView";
66    private static final boolean DEBUG = false;
67    protected static final int INVALID_PAGE = -1;
68
69    // the min drag distance for a fling to register, to prevent random page shifts
70    private static final int MIN_LENGTH_FOR_FLING = 25;
71
72    protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
73    protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
74    protected static final float NANOTIME_DIV = 1000000000.0f;
75
76    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
77    private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
78
79    private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
80    // The page is moved more than halfway, automatically move to the next page on touch up.
81    private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
82
83    // The following constants need to be scaled based on density. The scaled versions will be
84    // assigned to the corresponding member variables below.
85    private static final int FLING_THRESHOLD_VELOCITY = 500;
86    private static final int MIN_SNAP_VELOCITY = 1500;
87    private static final int MIN_FLING_VELOCITY = 250;
88
89    // We are disabling touch interaction of the widget region for factory ROM.
90    private static final boolean DISABLE_TOUCH_INTERACTION = false;
91    private static final boolean DISABLE_TOUCH_SIDE_PAGES = false;
92    private static final boolean DISABLE_FLING_TO_DELETE = true;
93
94    static final int AUTOMATIC_PAGE_SPACING = -1;
95
96    protected int mFlingThresholdVelocity;
97    protected int mMinFlingVelocity;
98    protected int mMinSnapVelocity;
99
100    protected float mDensity;
101    protected float mSmoothingTime;
102    protected float mTouchX;
103
104    protected boolean mFirstLayout = true;
105
106    protected int mCurrentPage;
107    protected int mChildCountOnLastMeasure;
108
109    protected int mNextPage = INVALID_PAGE;
110    protected int mMaxScrollX;
111    protected Scroller mScroller;
112    private VelocityTracker mVelocityTracker;
113
114    private float mParentDownMotionX;
115    private float mParentDownMotionY;
116    private float mDownMotionX;
117    private float mDownMotionY;
118    private float mDownScrollX;
119    protected float mLastMotionX;
120    protected float mLastMotionXRemainder;
121    protected float mLastMotionY;
122    protected float mTotalMotionX;
123    private int mLastScreenCenter = -1;
124    private int[] mChildOffsets;
125    private int[] mChildRelativeOffsets;
126
127    protected final static int TOUCH_STATE_REST = 0;
128    protected final static int TOUCH_STATE_SCROLLING = 1;
129    protected final static int TOUCH_STATE_PREV_PAGE = 2;
130    protected final static int TOUCH_STATE_NEXT_PAGE = 3;
131    protected final static int TOUCH_STATE_REORDERING = 4;
132
133    protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
134
135    protected int mTouchState = TOUCH_STATE_REST;
136    protected boolean mForceScreenScrolled = false;
137
138    protected OnLongClickListener mLongClickListener;
139
140    protected int mTouchSlop;
141    private int mPagingTouchSlop;
142    private int mMaximumVelocity;
143    protected int mPageSpacing;
144    protected int mPageLayoutPaddingTop;
145    protected int mPageLayoutPaddingBottom;
146    protected int mPageLayoutPaddingLeft;
147    protected int mPageLayoutPaddingRight;
148    protected int mPageLayoutWidthGap;
149    protected int mPageLayoutHeightGap;
150    protected int mCellCountX = 0;
151    protected int mCellCountY = 0;
152    protected boolean mCenterPagesVertically;
153    protected boolean mAllowOverScroll = true;
154    protected int mUnboundedScrollX;
155    protected int[] mTempVisiblePagesRange = new int[2];
156    protected boolean mForceDrawAllChildrenNextFrame;
157
158    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
159    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
160    // the screens from continuing to translate beyond the normal bounds.
161    protected int mOverScrollX;
162
163    protected static final int INVALID_POINTER = -1;
164
165    protected int mActivePointerId = INVALID_POINTER;
166
167    private PageSwitchListener mPageSwitchListener;
168
169    protected ArrayList<Boolean> mDirtyPageContent;
170
171    // If true, syncPages and syncPageItems will be called to refresh pages
172    protected boolean mContentIsRefreshable = true;
173
174    // If true, modify alpha of neighboring pages as user scrolls left/right
175    protected boolean mFadeInAdjacentScreens = false;
176
177    // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
178    // to switch to a new page
179    protected boolean mUsePagingTouchSlop = true;
180
181    // If true, the subclass should directly update scrollX itself in its computeScroll method
182    // (SmoothPagedView does this)
183    protected boolean mDeferScrollUpdate = false;
184
185    protected boolean mIsPageMoving = false;
186
187    // All syncs and layout passes are deferred until data is ready.
188    protected boolean mIsDataReady = false;
189
190    protected boolean mAllowLongPress = true;
191
192    // Scrolling indicator
193    private ValueAnimator mScrollIndicatorAnimator;
194    private View mScrollIndicator;
195    private int mScrollIndicatorPaddingLeft;
196    private int mScrollIndicatorPaddingRight;
197    private boolean mHasScrollIndicator = true;
198    private boolean mShouldShowScrollIndicator = false;
199    private boolean mShouldShowScrollIndicatorImmediately = false;
200    protected static final int sScrollIndicatorFadeInDuration = 150;
201    protected static final int sScrollIndicatorFadeOutDuration = 650;
202    protected static final int sScrollIndicatorFlashDuration = 650;
203    private boolean mScrollingPaused = false;
204
205    // The viewport whether the pages are to be contained (the actual view may be larger than the
206    // viewport)
207    private Rect mViewport = new Rect();
208
209    // Reordering
210    // We use the min scale to determine how much to expand the actually PagedView measured
211    // dimensions such that when we are zoomed out, the view is not clipped
212    private int REORDERING_DROP_REPOSITION_DURATION = 200;
213    protected int REORDERING_REORDER_REPOSITION_DURATION = 300;
214    protected int REORDERING_ZOOM_IN_OUT_DURATION = 250;
215    private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 300;
216    private float REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE = 0.1f;
217    private long REORDERING_DELETE_DROP_TARGET_FADE_DURATION = 150;
218    private float mMinScale = 1f;
219    protected View mDragView;
220    protected AnimatorSet mZoomInOutAnim;
221    private Runnable mSidePageHoverRunnable;
222    private int mSidePageHoverIndex = -1;
223    // This variable's scope is only for the duration of startReordering() and endReordering()
224    private boolean mReorderingStarted = false;
225    // This variable's scope is for the duration of startReordering() and after the zoomIn()
226    // animation after endReordering()
227    private boolean mIsReordering;
228    // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
229    private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
230    private int mPostReorderingPreZoomInRemainingAnimationCount;
231    private Runnable mPostReorderingPreZoomInRunnable;
232
233    // Edge swiping
234    private boolean mOnlyAllowEdgeSwipes = false;
235    private boolean mDownEventOnEdge = false;
236    private int mEdgeSwipeRegionSize = 0;
237
238    // Convenience/caching
239    private Matrix mTmpInvMatrix = new Matrix();
240    private float[] mTmpPoint = new float[2];
241    private Rect mTmpRect = new Rect();
242    private Rect mAltTmpRect = new Rect();
243
244    // Fling to delete
245    private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
246    private float FLING_TO_DELETE_FRICTION = 0.035f;
247    // The degrees specifies how much deviation from the up vector to still consider a fling "up"
248    private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
249    protected int mFlingToDeleteThresholdVelocity = -1400;
250    // Drag to delete
251    private boolean mDeferringForDelete = false;
252    private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
253    private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
254
255    // Drop to delete
256    private View mDeleteDropTarget;
257
258    private boolean mAutoComputePageSpacing = false;
259    private boolean mRecomputePageSpacing = false;
260
261    // Bouncer
262    private boolean mTopAlignPageWhenShrinkingForBouncer = false;
263
264    public interface PageSwitchListener {
265        void onPageSwitch(View newPage, int newPageIndex);
266    }
267
268    public PagedView(Context context) {
269        this(context, null);
270    }
271
272    public PagedView(Context context, AttributeSet attrs) {
273        this(context, attrs, 0);
274    }
275
276    public PagedView(Context context, AttributeSet attrs, int defStyle) {
277        super(context, attrs, defStyle);
278        TypedArray a = context.obtainStyledAttributes(attrs,
279                R.styleable.PagedView, defStyle, 0);
280        setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
281        if (mPageSpacing < 0) {
282            mAutoComputePageSpacing = mRecomputePageSpacing = true;
283        }
284        mPageLayoutPaddingTop = a.getDimensionPixelSize(
285                R.styleable.PagedView_pageLayoutPaddingTop, 0);
286        mPageLayoutPaddingBottom = a.getDimensionPixelSize(
287                R.styleable.PagedView_pageLayoutPaddingBottom, 0);
288        mPageLayoutPaddingLeft = a.getDimensionPixelSize(
289                R.styleable.PagedView_pageLayoutPaddingLeft, 0);
290        mPageLayoutPaddingRight = a.getDimensionPixelSize(
291                R.styleable.PagedView_pageLayoutPaddingRight, 0);
292        mPageLayoutWidthGap = a.getDimensionPixelSize(
293                R.styleable.PagedView_pageLayoutWidthGap, 0);
294        mPageLayoutHeightGap = a.getDimensionPixelSize(
295                R.styleable.PagedView_pageLayoutHeightGap, 0);
296        mScrollIndicatorPaddingLeft =
297            a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
298        mScrollIndicatorPaddingRight =
299            a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
300        a.recycle();
301
302        setHapticFeedbackEnabled(false);
303        init();
304    }
305
306    /**
307     * Initializes various states for this workspace.
308     */
309    protected void init() {
310        mDirtyPageContent = new ArrayList<Boolean>();
311        mDirtyPageContent.ensureCapacity(32);
312        mScroller = new Scroller(getContext(), new ScrollInterpolator());
313        mCurrentPage = 0;
314        mCenterPagesVertically = true;
315
316        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
317        mTouchSlop = configuration.getScaledPagingTouchSlop();
318        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
319        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
320        mDensity = getResources().getDisplayMetrics().density;
321
322        // Scale the fling-to-delete threshold by the density
323        mFlingToDeleteThresholdVelocity =
324                (int) (mFlingToDeleteThresholdVelocity * mDensity);
325
326        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
327        mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
328        mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
329        setOnHierarchyChangeListener(this);
330    }
331
332    void setDeleteDropTarget(View v) {
333        mDeleteDropTarget = v;
334    }
335
336    // Convenience methods to map points from self to parent and vice versa
337    float[] mapPointFromViewToParent(View v, float x, float y) {
338        mTmpPoint[0] = x;
339        mTmpPoint[1] = y;
340        v.getMatrix().mapPoints(mTmpPoint);
341        mTmpPoint[0] += v.getLeft();
342        mTmpPoint[1] += v.getTop();
343        return mTmpPoint;
344    }
345    float[] mapPointFromParentToView(View v, float x, float y) {
346        mTmpPoint[0] = x - v.getLeft();
347        mTmpPoint[1] = y - v.getTop();
348        v.getMatrix().invert(mTmpInvMatrix);
349        mTmpInvMatrix.mapPoints(mTmpPoint);
350        return mTmpPoint;
351    }
352
353    void updateDragViewTranslationDuringDrag() {
354        float x = mLastMotionX - mDownMotionX + getScrollX() - mDownScrollX;
355        float y = mLastMotionY - mDownMotionY;
356        mDragView.setTranslationX(x);
357        mDragView.setTranslationY(y);
358
359        if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): " + x + ", " + y);
360    }
361
362    public void setMinScale(float f) {
363        mMinScale = f;
364        requestLayout();
365    }
366
367    @Override
368    public void setScaleX(float scaleX) {
369        super.setScaleX(scaleX);
370        if (isReordering(true)) {
371            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
372            mLastMotionX = p[0];
373            mLastMotionY = p[1];
374            updateDragViewTranslationDuringDrag();
375        }
376    }
377
378    // Convenience methods to get the actual width/height of the PagedView (since it is measured
379    // to be larger to account for the minimum possible scale)
380    int getViewportWidth() {
381        return mViewport.width();
382    }
383    int getViewportHeight() {
384        return mViewport.height();
385    }
386
387    // Convenience methods to get the offset ASSUMING that we are centering the pages in the
388    // PagedView both horizontally and vertically
389    int getViewportOffsetX() {
390        return (getMeasuredWidth() - getViewportWidth()) / 2;
391    }
392
393    int getViewportOffsetY() {
394        return (getMeasuredHeight() - getViewportHeight()) / 2;
395    }
396
397    public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
398        mPageSwitchListener = pageSwitchListener;
399        if (mPageSwitchListener != null) {
400            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
401        }
402    }
403
404    /**
405     * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
406     */
407    public boolean isLayoutRtl() {
408        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
409    }
410
411    /**
412     * Called by subclasses to mark that data is ready, and that we can begin loading and laying
413     * out pages.
414     */
415    protected void setDataIsReady() {
416        mIsDataReady = true;
417    }
418
419    protected boolean isDataReady() {
420        return mIsDataReady;
421    }
422
423    /**
424     * Returns the index of the currently displayed page.
425     *
426     * @return The index of the currently displayed page.
427     */
428    int getCurrentPage() {
429        return mCurrentPage;
430    }
431
432    int getNextPage() {
433        return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
434    }
435
436    int getPageCount() {
437        return getChildCount();
438    }
439
440    View getPageAt(int index) {
441        return getChildAt(index);
442    }
443
444    protected int indexToPage(int index) {
445        return index;
446    }
447
448    /**
449     * Updates the scroll of the current page immediately to its final scroll position.  We use this
450     * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
451     * the previous tab page.
452     */
453    protected void updateCurrentPageScroll() {
454        // If the current page is invalid, just reset the scroll position to zero
455        int newX = 0;
456        if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
457            int offset = getChildOffset(mCurrentPage);
458            int relOffset = getRelativeChildOffset(mCurrentPage);
459            newX = offset - relOffset;
460        }
461        scrollTo(newX, 0);
462        mScroller.setFinalX(newX);
463        mScroller.forceFinished(true);
464    }
465
466    /**
467     * Called during AllApps/Home transitions to avoid unnecessary work. When that other animation
468     * ends, {@link #resumeScrolling()} should be called, along with
469     * {@link #updateCurrentPageScroll()} to correctly set the final state and re-enable scrolling.
470     */
471    void pauseScrolling() {
472        mScroller.forceFinished(true);
473        cancelScrollingIndicatorAnimations();
474        mScrollingPaused = true;
475    }
476
477    /**
478     * Enables scrolling again.
479     * @see #pauseScrolling()
480     */
481    void resumeScrolling() {
482        mScrollingPaused = false;
483    }
484    /**
485     * Sets the current page.
486     */
487    void setCurrentPage(int currentPage) {
488        if (!mScroller.isFinished()) {
489            mScroller.abortAnimation();
490        }
491        // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
492        // the default
493        if (getChildCount() == 0) {
494            return;
495        }
496
497        mForceScreenScrolled = true;
498        mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
499        updateCurrentPageScroll();
500        updateScrollingIndicator();
501        notifyPageSwitchListener();
502        invalidate();
503    }
504
505    protected void notifyPageSwitchListener() {
506        if (mPageSwitchListener != null) {
507            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
508        }
509    }
510    protected void pageBeginMoving() {
511        if (!mIsPageMoving) {
512            mIsPageMoving = true;
513            onPageBeginMoving();
514        }
515    }
516
517    protected void pageEndMoving() {
518        if (mIsPageMoving) {
519            mIsPageMoving = false;
520            onPageEndMoving();
521        }
522    }
523
524    protected boolean isPageMoving() {
525        return mIsPageMoving;
526    }
527
528    // a method that subclasses can override to add behavior
529    protected void onPageBeginMoving() {
530    }
531
532    // a method that subclasses can override to add behavior
533    protected void onPageEndMoving() {
534    }
535
536    /**
537     * Registers the specified listener on each page contained in this workspace.
538     *
539     * @param l The listener used to respond to long clicks.
540     */
541    @Override
542    public void setOnLongClickListener(OnLongClickListener l) {
543        mLongClickListener = l;
544        final int count = getPageCount();
545        for (int i = 0; i < count; i++) {
546            getPageAt(i).setOnLongClickListener(l);
547        }
548    }
549
550    @Override
551    public void scrollBy(int x, int y) {
552        scrollTo(mUnboundedScrollX + x, getScrollY() + y);
553    }
554
555    @Override
556    public void scrollTo(int x, int y) {
557        final boolean isRtl = isLayoutRtl();
558        mUnboundedScrollX = x;
559
560        boolean isXBeforeFirstPage = isRtl ? (x > mMaxScrollX) : (x < 0);
561        boolean isXAfterLastPage = isRtl ? (x < 0) : (x > mMaxScrollX);
562        if (isXBeforeFirstPage) {
563            super.scrollTo(0, y);
564            if (mAllowOverScroll) {
565                if (isRtl) {
566                    overScroll(x - mMaxScrollX);
567                } else {
568                    overScroll(x);
569                }
570            }
571        } else if (isXAfterLastPage) {
572            super.scrollTo(mMaxScrollX, y);
573            if (mAllowOverScroll) {
574                if (isRtl) {
575                    overScroll(x);
576                } else {
577                    overScroll(x - mMaxScrollX);
578                }
579            }
580        } else {
581            mOverScrollX = x;
582            super.scrollTo(x, y);
583        }
584
585        mTouchX = x;
586        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
587
588        // Update the last motion events when scrolling
589        if (isReordering(true)) {
590            float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
591            mLastMotionX = p[0];
592            mLastMotionY = p[1];
593            updateDragViewTranslationDuringDrag();
594        }
595    }
596
597    // we moved this functionality to a helper function so SmoothPagedView can reuse it
598    protected boolean computeScrollHelper() {
599        if (mScroller.computeScrollOffset()) {
600            // Don't bother scrolling if the page does not need to be moved
601            if (getScrollX() != mScroller.getCurrX()
602                || getScrollY() != mScroller.getCurrY()
603                || mOverScrollX != mScroller.getCurrX()) {
604                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
605            }
606            invalidate();
607            return true;
608        } else if (mNextPage != INVALID_PAGE) {
609            mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
610            mNextPage = INVALID_PAGE;
611            notifyPageSwitchListener();
612
613            // We don't want to trigger a page end moving unless the page has settled
614            // and the user has stopped scrolling
615            if (mTouchState == TOUCH_STATE_REST) {
616                pageEndMoving();
617            }
618
619            onPostReorderingAnimationCompleted();
620            // Notify the user when the page changes
621            AccessibilityManager accessibilityManager = (AccessibilityManager)
622                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
623            if (accessibilityManager.isEnabled()) {
624                AccessibilityEvent ev =
625                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
626                ev.getText().add(getCurrentPageDescription());
627                sendAccessibilityEventUnchecked(ev);
628            }
629            return true;
630        }
631        return false;
632    }
633
634    @Override
635    public void computeScroll() {
636        computeScrollHelper();
637    }
638
639    protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
640        return mTopAlignPageWhenShrinkingForBouncer;
641    }
642
643    public static class LayoutParams extends ViewGroup.LayoutParams {
644        public boolean isFullScreenPage = false;
645
646        /**
647         * {@inheritDoc}
648         */
649        public LayoutParams(int width, int height) {
650            super(width, height);
651        }
652
653        public LayoutParams(ViewGroup.LayoutParams source) {
654            super(source);
655        }
656    }
657
658    protected LayoutParams generateDefaultLayoutParams() {
659        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
660    }
661
662    public void addFullScreenPage(View page, int width, int height) {
663        LayoutParams lp = generateDefaultLayoutParams();
664        lp.isFullScreenPage = true;
665        super.addView(page, 0, lp);
666    }
667
668    @Override
669    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
670        if (!mIsDataReady || getChildCount() == 0) {
671            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
672            return;
673        }
674
675        // We measure the dimensions of the PagedView to be larger than the pages so that when we
676        // zoom out (and scale down), the view is still contained in the parent
677        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
678        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
679        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
680        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
681        // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the
682        // viewport, we can be at most one and a half screens offset once we scale down
683        DisplayMetrics dm = getResources().getDisplayMetrics();
684        int maxSize = Math.max(dm.widthPixels, dm.heightPixels);
685        int parentWidthSize = (int) (1.5f * maxSize);
686        int parentHeightSize = maxSize;
687        int scaledWidthSize = (int) (parentWidthSize / mMinScale);
688        int scaledHeightSize = (int) (parentHeightSize / mMinScale);
689        mViewport.set(0, 0, widthSize, heightSize);
690
691        if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
692            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
693            return;
694        }
695
696        // Return early if we aren't given a proper dimension
697        if (widthSize <= 0 || heightSize <= 0) {
698            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
699            return;
700        }
701
702        /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
703         * of the All apps view on XLarge displays to not take up more space then it needs. Width
704         * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
705         * each page to have the same width.
706         */
707        final int verticalPadding = getPaddingTop() + getPaddingBottom();
708        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
709
710        // The children are given the same width and height as the workspace
711        // unless they were set to WRAP_CONTENT
712        if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
713        if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
714        if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
715        if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
716        if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
717        final int childCount = getChildCount();
718        for (int i = 0; i < childCount; i++) {
719            // disallowing padding in paged view (just pass 0)
720            final View child = getPageAt(i);
721            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
722
723            int childWidthMode;
724            int childHeightMode;
725            int childWidth;
726            int childHeight;
727
728            if (!lp.isFullScreenPage) {
729                if (lp.width == LayoutParams.WRAP_CONTENT) {
730                    childWidthMode = MeasureSpec.AT_MOST;
731                } else {
732                    childWidthMode = MeasureSpec.EXACTLY;
733                }
734
735                if (lp.height == LayoutParams.WRAP_CONTENT) {
736                    childHeightMode = MeasureSpec.AT_MOST;
737                } else {
738                    childHeightMode = MeasureSpec.EXACTLY;
739                }
740
741                childWidth = widthSize - horizontalPadding;
742                childHeight = heightSize - verticalPadding;
743
744            } else {
745                childWidthMode = MeasureSpec.EXACTLY;
746                childHeightMode = MeasureSpec.EXACTLY;
747
748                childWidth = getViewportWidth();
749                childHeight = getViewportHeight();
750            }
751
752            final int childWidthMeasureSpec =
753                    MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
754                final int childHeightMeasureSpec =
755                    MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
756            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
757        }
758        setMeasuredDimension(scaledWidthSize, scaledHeightSize);
759
760        // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
761        // We also wait until we set the measured dimensions before flushing the cache as well, to
762        // ensure that the cache is filled with good values.
763        invalidateCachedOffsets();
764
765        if (childCount > 0) {
766            if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getViewportWidth() + ", "
767                    + getChildWidth(0));
768
769            // Calculate the variable page spacing if necessary
770            if (mAutoComputePageSpacing && mRecomputePageSpacing) {
771                // The gap between pages in the PagedView should be equal to the gap from the page
772                // to the edge of the screen (so it is not visible in the current screen).  To
773                // account for unequal padding on each side of the paged view, we take the maximum
774                // of the left/right gap and use that as the gap between each page.
775                int offset = getRelativeChildOffset(0);
776                int spacing = Math.max(offset, widthSize - offset -
777                        getChildAt(0).getMeasuredWidth());
778                setPageSpacing(spacing);
779                mRecomputePageSpacing = false;
780            }
781        }
782
783        if (mScroller.isFinished() && mChildCountOnLastMeasure != getChildCount() &&
784                !mDeferringForDelete) {
785            setCurrentPage(getNextPage());
786        }
787        mChildCountOnLastMeasure = getChildCount();
788
789        updateScrollingIndicatorPosition();
790
791        if (childCount > 0) {
792            final int index = isLayoutRtl() ? 0 : childCount - 1;
793            mMaxScrollX = getChildOffset(index) - getRelativeChildOffset(index);
794        } else {
795            mMaxScrollX = 0;
796        }
797    }
798
799    public void setPageSpacing(int pageSpacing) {
800        mPageSpacing = pageSpacing;
801        invalidateCachedOffsets();
802    }
803
804    @Override
805    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
806        if (!mIsDataReady || getChildCount() == 0) {
807            return;
808        }
809
810        if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
811        final int childCount = getChildCount();
812
813        int offsetX = getViewportOffsetX();
814        int offsetY = getViewportOffsetY();
815
816        // Update the viewport offsets
817        mViewport.offset(offsetX,  offsetY);
818
819        final boolean isRtl = isLayoutRtl();
820
821        final int startIndex = isRtl ? childCount - 1 : 0;
822        final int endIndex = isRtl ? -1 : childCount;
823        final int delta = isRtl ? -1 : 1;
824
825        int verticalPadding = getPaddingTop() + getPaddingBottom();
826        int childLeft = offsetX + getRelativeChildOffset(startIndex);
827        for (int i = startIndex; i != endIndex; i += delta) {
828            final View child = getPageAt(i);
829            LayoutParams lp = (LayoutParams) child.getLayoutParams();
830            int childTop;
831
832            if (lp.isFullScreenPage) {
833                childTop = offsetY;
834            } else {
835                childTop = offsetY + getPaddingTop();
836                if (mCenterPagesVertically) {
837                    childTop += ((getViewportHeight() - verticalPadding) - child.getMeasuredHeight()) / 2;
838                }
839            }
840
841            if (child.getVisibility() != View.GONE) {
842                final int childWidth = child.getMeasuredWidth();
843                final int childHeight = child.getMeasuredHeight();
844
845                if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
846                child.layout(childLeft, childTop,
847                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
848                childLeft += childWidth + mPageSpacing;
849            }
850        }
851
852        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
853            setHorizontalScrollBarEnabled(false);
854            updateCurrentPageScroll();
855            setHorizontalScrollBarEnabled(true);
856            mFirstLayout = false;
857        }
858    }
859
860    protected void screenScrolled(int screenCenter) {
861        if (isScrollingIndicatorEnabled()) {
862            updateScrollingIndicator();
863        }
864        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
865
866        if (mFadeInAdjacentScreens && !isInOverscroll) {
867            for (int i = 0; i < getChildCount(); i++) {
868                View child = getChildAt(i);
869                if (child != null) {
870                    float scrollProgress = getScrollProgress(screenCenter, child, i);
871                    float alpha = 1 - Math.abs(scrollProgress);
872                    child.setAlpha(alpha);
873                }
874            }
875            invalidate();
876        }
877    }
878
879    @Override
880    public void onChildViewAdded(View parent, View child) {
881        // This ensures that when children are added, they get the correct transforms / alphas
882        // in accordance with any scroll effects.
883        mForceScreenScrolled = true;
884        mRecomputePageSpacing = true;
885
886        invalidate();
887        invalidateCachedOffsets();
888    }
889
890    @Override
891    public void onChildViewRemoved(View parent, View child) {
892        mForceScreenScrolled = true;
893        invalidate();
894        invalidateCachedOffsets();
895    }
896
897    protected void invalidateCachedOffsets() {
898        int count = getChildCount();
899        if (count == 0) {
900            mChildOffsets = null;
901            mChildRelativeOffsets = null;
902            return;
903        }
904
905        mChildOffsets = new int[count];
906        mChildRelativeOffsets = new int[count];
907        for (int i = 0; i < count; i++) {
908            mChildOffsets[i] = -1;
909            mChildRelativeOffsets[i] = -1;
910        }
911    }
912
913    protected int getChildOffset(int index) {
914        if (index < 0 || index > getChildCount() - 1) return 0;
915
916        final boolean isRtl = isLayoutRtl();
917        int[] childOffsets = mChildOffsets;
918
919        if (childOffsets != null && childOffsets[index] != -1) {
920            return childOffsets[index];
921        } else {
922            if (getChildCount() == 0)
923                return 0;
924
925            final int startIndex = isRtl ? getChildCount() - 1 : 0;
926            final int endIndex = isRtl ? index : index;
927            final int delta = isRtl ? -1 : 1;
928
929            int offset = getRelativeChildOffset(startIndex);
930            for (int i = startIndex; i != endIndex; i += delta) {
931                offset += getPageAt(i).getMeasuredWidth() + mPageSpacing;
932            }
933            if (childOffsets != null) {
934                childOffsets[index] = offset;
935            }
936            return offset;
937        }
938
939    }
940
941    protected int getRelativeChildOffset(int index) {
942        if (index < 0 || index > getChildCount() - 1) return 0;
943
944        if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
945            return mChildRelativeOffsets[index];
946        } else {
947            final int padding = getPaddingLeft() + getPaddingRight();
948            final int offset = getPaddingLeft() +
949                    (getViewportWidth() - padding - getChildWidth(index)) / 2;
950            if (mChildRelativeOffsets != null) {
951                mChildRelativeOffsets[index] = offset;
952            }
953            return offset;
954        }
955    }
956
957    void boundByReorderablePages(boolean isReordering, int[] range) {
958        // Do nothing
959    }
960
961    // TODO: Fix this
962    protected void getVisiblePages(int[] range) {
963        range[0] = 0;
964        range[1] = getPageCount() - 1;
965
966        /*
967        final int pageCount = getChildCount();
968
969        if (pageCount > 0) {
970            final int screenWidth = getViewportWidth();
971            int leftScreen = 0;
972            int rightScreen = 0;
973            int offsetX = getViewportOffsetX() + getScrollX();
974            View currPage = getPageAt(leftScreen);
975            while (leftScreen < pageCount - 1 &&
976                    currPage.getX() + currPage.getWidth() -
977                    currPage.getPaddingRight() < offsetX) {
978                leftScreen++;
979                currPage = getPageAt(leftScreen);
980            }
981            rightScreen = leftScreen;
982            currPage = getPageAt(rightScreen + 1);
983            while (rightScreen < pageCount - 1 &&
984                    currPage.getX() - currPage.getPaddingLeft() < offsetX + screenWidth) {
985                rightScreen++;
986                currPage = getPageAt(rightScreen + 1);
987            }
988
989            // TEMP: this is a hacky way to ensure that animations to new pages are not clipped
990            // because we don't draw them while scrolling?
991            range[0] = Math.max(0, leftScreen - 1);
992            range[1] = Math.min(rightScreen + 1, getChildCount() - 1);
993        } else {
994            range[0] = -1;
995            range[1] = -1;
996        }
997        */
998    }
999
1000    protected boolean shouldDrawChild(View child) {
1001        return child.getAlpha() > 0;
1002    }
1003
1004    @Override
1005    protected void dispatchDraw(Canvas canvas) {
1006        int halfScreenSize = getViewportWidth() / 2;
1007        // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
1008        // Otherwise it is equal to the scaled overscroll position.
1009        int screenCenter = mOverScrollX + halfScreenSize;
1010
1011        if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
1012            // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
1013            // set it for the next frame
1014            mForceScreenScrolled = false;
1015            screenScrolled(screenCenter);
1016            mLastScreenCenter = screenCenter;
1017        }
1018
1019        // Find out which screens are visible; as an optimization we only call draw on them
1020        final int pageCount = getChildCount();
1021        if (pageCount > 0) {
1022            getVisiblePages(mTempVisiblePagesRange);
1023            final int leftScreen = mTempVisiblePagesRange[0];
1024            final int rightScreen = mTempVisiblePagesRange[1];
1025            if (leftScreen != -1 && rightScreen != -1) {
1026                final long drawingTime = getDrawingTime();
1027                // Clip to the bounds
1028                canvas.save();
1029                canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
1030                        getScrollY() + getBottom() - getTop());
1031
1032                // Draw all the children, leaving the drag view for last
1033                for (int i = pageCount - 1; i >= 0; i--) {
1034                    final View v = getPageAt(i);
1035                    if (v == mDragView) continue;
1036                    if (mForceDrawAllChildrenNextFrame ||
1037                               (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
1038                        drawChild(canvas, v, drawingTime);
1039                    }
1040                }
1041                // Draw the drag view on top (if there is one)
1042                if (mDragView != null) {
1043                    drawChild(canvas, mDragView, drawingTime);
1044                }
1045
1046                mForceDrawAllChildrenNextFrame = false;
1047                canvas.restore();
1048            }
1049        }
1050    }
1051
1052    @Override
1053    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
1054        int page = indexToPage(indexOfChild(child));
1055        if (page != mCurrentPage || !mScroller.isFinished()) {
1056            snapToPage(page);
1057            return true;
1058        }
1059        return false;
1060    }
1061
1062    @Override
1063    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1064        int focusablePage;
1065        if (mNextPage != INVALID_PAGE) {
1066            focusablePage = mNextPage;
1067        } else {
1068            focusablePage = mCurrentPage;
1069        }
1070        View v = getPageAt(focusablePage);
1071        if (v != null) {
1072            return v.requestFocus(direction, previouslyFocusedRect);
1073        }
1074        return false;
1075    }
1076
1077    @Override
1078    public boolean dispatchUnhandledMove(View focused, int direction) {
1079        // XXX-RTL: This will be fixed in a future CL
1080        if (direction == View.FOCUS_LEFT) {
1081            if (getCurrentPage() > 0) {
1082                snapToPage(getCurrentPage() - 1);
1083                return true;
1084            }
1085        } else if (direction == View.FOCUS_RIGHT) {
1086            if (getCurrentPage() < getPageCount() - 1) {
1087                snapToPage(getCurrentPage() + 1);
1088                return true;
1089            }
1090        }
1091        return super.dispatchUnhandledMove(focused, direction);
1092    }
1093
1094    @Override
1095    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1096        // XXX-RTL: This will be fixed in a future CL
1097        if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
1098            getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
1099        }
1100        if (direction == View.FOCUS_LEFT) {
1101            if (mCurrentPage > 0) {
1102                getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
1103            }
1104        } else if (direction == View.FOCUS_RIGHT){
1105            if (mCurrentPage < getPageCount() - 1) {
1106                getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
1107            }
1108        }
1109    }
1110
1111    /**
1112     * If one of our descendant views decides that it could be focused now, only
1113     * pass that along if it's on the current page.
1114     *
1115     * This happens when live folders requery, and if they're off page, they
1116     * end up calling requestFocus, which pulls it on page.
1117     */
1118    @Override
1119    public void focusableViewAvailable(View focused) {
1120        View current = getPageAt(mCurrentPage);
1121        View v = focused;
1122        while (true) {
1123            if (v == current) {
1124                super.focusableViewAvailable(focused);
1125                return;
1126            }
1127            if (v == this) {
1128                return;
1129            }
1130            ViewParent parent = v.getParent();
1131            if (parent instanceof View) {
1132                v = (View)v.getParent();
1133            } else {
1134                return;
1135            }
1136        }
1137    }
1138
1139    /**
1140     * {@inheritDoc}
1141     */
1142    @Override
1143    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1144        if (disallowIntercept) {
1145            // We need to make sure to cancel our long press if
1146            // a scrollable widget takes over touch events
1147            final View currentPage = getPageAt(mCurrentPage);
1148            currentPage.cancelLongPress();
1149        }
1150        super.requestDisallowInterceptTouchEvent(disallowIntercept);
1151    }
1152
1153    /**
1154     * Return true if a tap at (x, y) should trigger a flip to the previous page.
1155     */
1156    protected boolean hitsPreviousPage(float x, float y) {
1157        if (isLayoutRtl()) {
1158            return (x > (getViewportOffsetX() + getViewportWidth() -
1159                    getRelativeChildOffset(mCurrentPage) + mPageSpacing));
1160        }
1161        return (x < getViewportOffsetX() + getRelativeChildOffset(mCurrentPage) - mPageSpacing);
1162    }
1163
1164    /**
1165     * Return true if a tap at (x, y) should trigger a flip to the next page.
1166     */
1167    protected boolean hitsNextPage(float x, float y) {
1168        if (isLayoutRtl()) {
1169            return (x < getViewportOffsetX() + getRelativeChildOffset(mCurrentPage) - mPageSpacing);
1170        }
1171        return  (x > (getViewportOffsetX() + getViewportWidth() -
1172                getRelativeChildOffset(mCurrentPage) + mPageSpacing));
1173    }
1174
1175    /** Returns whether x and y originated within the buffered viewport */
1176    private boolean isTouchPointInViewportWithBuffer(int x, int y) {
1177        mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
1178                mViewport.right + mViewport.width() / 2, mViewport.bottom);
1179        return mTmpRect.contains(x, y);
1180    }
1181
1182    /** Returns whether x and y originated within the current page view bounds */
1183    private boolean isTouchPointInCurrentPage(int x, int y) {
1184        View v = getPageAt(getCurrentPage());
1185        if (v != null) {
1186            mTmpRect.set((v.getLeft() - getScrollX()), 0, (v.getRight() - getScrollX()),
1187                    v.getBottom());
1188            return mTmpRect.contains(x, y);
1189        }
1190        return false;
1191    }
1192
1193    @Override
1194    public boolean onInterceptTouchEvent(MotionEvent ev) {
1195        if (DISABLE_TOUCH_INTERACTION) {
1196            return false;
1197        }
1198
1199        /*
1200         * This method JUST determines whether we want to intercept the motion.
1201         * If we return true, onTouchEvent will be called and we do the actual
1202         * scrolling there.
1203         */
1204        acquireVelocityTrackerAndAddMovement(ev);
1205
1206        // Skip touch handling if there are no pages to swipe
1207        if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
1208
1209        /*
1210         * Shortcut the most recurring case: the user is in the dragging
1211         * state and he is moving his finger.  We want to intercept this
1212         * motion.
1213         */
1214        final int action = ev.getAction();
1215        if ((action == MotionEvent.ACTION_MOVE) &&
1216                (mTouchState == TOUCH_STATE_SCROLLING)) {
1217            return true;
1218        }
1219
1220        switch (action & MotionEvent.ACTION_MASK) {
1221            case MotionEvent.ACTION_MOVE: {
1222                /*
1223                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1224                 * whether the user has moved far enough from his original down touch.
1225                 */
1226                if (mActivePointerId != INVALID_POINTER) {
1227                    determineScrollingStart(ev);
1228                    break;
1229                }
1230                // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
1231                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
1232                // i.e. fall through to the next case (don't break)
1233                // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
1234                // while it's small- this was causing a crash before we checked for INVALID_POINTER)
1235            }
1236
1237            case MotionEvent.ACTION_DOWN: {
1238                final float x = ev.getX();
1239                final float y = ev.getY();
1240                // Remember location of down touch
1241                mDownMotionX = x;
1242                mDownMotionY = y;
1243                mDownScrollX = getScrollX();
1244                mLastMotionX = x;
1245                mLastMotionY = y;
1246                float[] p = mapPointFromViewToParent(this, x, y);
1247                mParentDownMotionX = p[0];
1248                mParentDownMotionY = p[1];
1249                mLastMotionXRemainder = 0;
1250                mTotalMotionX = 0;
1251                mActivePointerId = ev.getPointerId(0);
1252
1253                // Determine if the down event is within the threshold to be an edge swipe
1254                int leftEdgeBoundary = getViewportOffsetX() + mEdgeSwipeRegionSize;
1255                int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize;
1256                if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) {
1257                    mDownEventOnEdge = true;
1258                }
1259
1260                /*
1261                 * If being flinged and user touches the screen, initiate drag;
1262                 * otherwise don't.  mScroller.isFinished should be false when
1263                 * being flinged.
1264                 */
1265                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
1266                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
1267                if (finishedScrolling) {
1268                    mTouchState = TOUCH_STATE_REST;
1269                    mScroller.abortAnimation();
1270                } else {
1271                    if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
1272                        mTouchState = TOUCH_STATE_SCROLLING;
1273                    } else {
1274                        mTouchState = TOUCH_STATE_REST;
1275                    }
1276                }
1277
1278                // check if this can be the beginning of a tap on the side of the pages
1279                // to scroll the current page
1280                if (!DISABLE_TOUCH_SIDE_PAGES) {
1281                    if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
1282                        if (getChildCount() > 0) {
1283                            if (hitsPreviousPage(x, y)) {
1284                                mTouchState = TOUCH_STATE_PREV_PAGE;
1285                            } else if (hitsNextPage(x, y)) {
1286                                mTouchState = TOUCH_STATE_NEXT_PAGE;
1287                            }
1288                        }
1289                    }
1290                }
1291                break;
1292            }
1293
1294            case MotionEvent.ACTION_UP:
1295            case MotionEvent.ACTION_CANCEL:
1296                resetTouchState();
1297                // Just intercept the touch event on up if we tap outside the strict viewport
1298                if (!isTouchPointInCurrentPage((int) mLastMotionX, (int) mLastMotionY)) {
1299                    return true;
1300                }
1301                break;
1302
1303            case MotionEvent.ACTION_POINTER_UP:
1304                onSecondaryPointerUp(ev);
1305                releaseVelocityTracker();
1306                break;
1307        }
1308
1309        /*
1310         * The only time we want to intercept motion events is if we are in the
1311         * drag mode.
1312         */
1313        return mTouchState != TOUCH_STATE_REST;
1314    }
1315
1316    protected void determineScrollingStart(MotionEvent ev) {
1317        determineScrollingStart(ev, 1.0f);
1318    }
1319
1320    /*
1321     * Determines if we should change the touch state to start scrolling after the
1322     * user moves their touch point too far.
1323     */
1324    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1325        // Disallow scrolling if we don't have a valid pointer index
1326        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1327        if (pointerIndex == -1) return;
1328
1329        // Disallow scrolling if we started the gesture from outside the viewport
1330        final float x = ev.getX(pointerIndex);
1331        final float y = ev.getY(pointerIndex);
1332        if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
1333
1334        // If we're only allowing edge swipes, we break out early if the down event wasn't
1335        // at the edge.
1336        if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) return;
1337
1338        final int xDiff = (int) Math.abs(x - mLastMotionX);
1339        final int yDiff = (int) Math.abs(y - mLastMotionY);
1340
1341        final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
1342        boolean xPaged = xDiff > mPagingTouchSlop;
1343        boolean xMoved = xDiff > touchSlop;
1344        boolean yMoved = yDiff > touchSlop;
1345
1346        if (xMoved || xPaged || yMoved) {
1347            if (mUsePagingTouchSlop ? xPaged : xMoved) {
1348                // Scroll if the user moved far enough along the X axis
1349                mTouchState = TOUCH_STATE_SCROLLING;
1350                mTotalMotionX += Math.abs(mLastMotionX - x);
1351                mLastMotionX = x;
1352                mLastMotionXRemainder = 0;
1353                mTouchX = getViewportOffsetX() + getScrollX();
1354                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1355                pageBeginMoving();
1356            }
1357        }
1358    }
1359
1360    protected float getMaxScrollProgress() {
1361        return 1.0f;
1362    }
1363
1364    protected void cancelCurrentPageLongPress() {
1365        if (mAllowLongPress) {
1366            //mAllowLongPress = false;
1367            // Try canceling the long press. It could also have been scheduled
1368            // by a distant descendant, so use the mAllowLongPress flag to block
1369            // everything
1370            final View currentPage = getPageAt(mCurrentPage);
1371            if (currentPage != null) {
1372                currentPage.cancelLongPress();
1373            }
1374        }
1375    }
1376
1377    protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
1378        final int halfScreenSize = getViewportWidth() / 2;
1379
1380        screenCenter = Math.min(getScrollX() + halfScreenSize, screenCenter);
1381        screenCenter = Math.max(halfScreenSize,  screenCenter);
1382
1383        return getScrollProgress(screenCenter, v, page);
1384    }
1385
1386    protected float getScrollProgress(int screenCenter, View v, int page) {
1387        final int halfScreenSize = getViewportWidth() / 2;
1388
1389        int totalDistance = v.getMeasuredWidth() + mPageSpacing;
1390        int delta = screenCenter - (getChildOffset(page) -
1391                getRelativeChildOffset(page) + halfScreenSize);
1392
1393        float scrollProgress = delta / (totalDistance * 1.0f);
1394        scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
1395        scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
1396        return scrollProgress;
1397    }
1398
1399    // This curve determines how the effect of scrolling over the limits of the page dimishes
1400    // as the user pulls further and further from the bounds
1401    private float overScrollInfluenceCurve(float f) {
1402        f -= 1.0f;
1403        return f * f * f + 1.0f;
1404    }
1405
1406    protected void acceleratedOverScroll(float amount) {
1407        int screenSize = getViewportWidth();
1408
1409        // We want to reach the max over scroll effect when the user has
1410        // over scrolled half the size of the screen
1411        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1412
1413        if (f == 0) return;
1414
1415        // Clamp this factor, f, to -1 < f < 1
1416        if (Math.abs(f) >= 1) {
1417            f /= Math.abs(f);
1418        }
1419
1420        int overScrollAmount = (int) Math.round(f * screenSize);
1421        if (amount < 0) {
1422            mOverScrollX = overScrollAmount;
1423            super.scrollTo(0, getScrollY());
1424        } else {
1425            mOverScrollX = mMaxScrollX + overScrollAmount;
1426            super.scrollTo(mMaxScrollX, getScrollY());
1427        }
1428        invalidate();
1429    }
1430
1431    protected void dampedOverScroll(float amount) {
1432        int screenSize = getViewportWidth();
1433
1434        float f = (amount / screenSize);
1435
1436        if (f == 0) return;
1437        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1438
1439        // Clamp this factor, f, to -1 < f < 1
1440        if (Math.abs(f) >= 1) {
1441            f /= Math.abs(f);
1442        }
1443
1444        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
1445        if (amount < 0) {
1446            mOverScrollX = overScrollAmount;
1447            super.scrollTo(0, getScrollY());
1448        } else {
1449            mOverScrollX = mMaxScrollX + overScrollAmount;
1450            super.scrollTo(mMaxScrollX, getScrollY());
1451        }
1452        invalidate();
1453    }
1454
1455    protected void overScroll(float amount) {
1456        dampedOverScroll(amount);
1457    }
1458
1459    protected float maxOverScroll() {
1460        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1461        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1462        float f = 1.0f;
1463        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1464        return OVERSCROLL_DAMP_FACTOR * f;
1465    }
1466
1467    @Override
1468    public boolean onTouchEvent(MotionEvent ev) {
1469        if (DISABLE_TOUCH_INTERACTION) {
1470            return false;
1471        }
1472
1473        // Skip touch handling if there are no pages to swipe
1474        if (getChildCount() <= 0) return super.onTouchEvent(ev);
1475
1476        acquireVelocityTrackerAndAddMovement(ev);
1477
1478        final int action = ev.getAction();
1479
1480        switch (action & MotionEvent.ACTION_MASK) {
1481        case MotionEvent.ACTION_DOWN:
1482            /*
1483             * If being flinged and user touches, stop the fling. isFinished
1484             * will be false if being flinged.
1485             */
1486            if (!mScroller.isFinished()) {
1487                mScroller.abortAnimation();
1488            }
1489
1490            // Remember where the motion event started
1491            mDownMotionX = mLastMotionX = ev.getX();
1492            mDownMotionY = mLastMotionY = ev.getY();
1493            mDownScrollX = getScrollX();
1494            float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1495            mParentDownMotionX = p[0];
1496            mParentDownMotionY = p[1];
1497            mLastMotionXRemainder = 0;
1498            mTotalMotionX = 0;
1499            mActivePointerId = ev.getPointerId(0);
1500
1501            // Determine if the down event is within the threshold to be an edge swipe
1502            int leftEdgeBoundary = getViewportOffsetX() + mEdgeSwipeRegionSize;
1503            int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize;
1504            if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) {
1505                mDownEventOnEdge = true;
1506            }
1507
1508            if (mTouchState == TOUCH_STATE_SCROLLING) {
1509                pageBeginMoving();
1510            }
1511            break;
1512
1513        case MotionEvent.ACTION_MOVE:
1514            if (mTouchState == TOUCH_STATE_SCROLLING) {
1515                // Scroll to follow the motion event
1516                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1517
1518                if (pointerIndex == -1) return true;
1519
1520                final float x = ev.getX(pointerIndex);
1521                final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1522
1523                mTotalMotionX += Math.abs(deltaX);
1524
1525                // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
1526                // keep the remainder because we are actually testing if we've moved from the last
1527                // scrolled position (which is discrete).
1528                if (Math.abs(deltaX) >= 1.0f) {
1529                    mTouchX += deltaX;
1530                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1531                    if (!mDeferScrollUpdate) {
1532                        scrollBy((int) deltaX, 0);
1533                        if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
1534                    } else {
1535                        invalidate();
1536                    }
1537                    mLastMotionX = x;
1538                    mLastMotionXRemainder = deltaX - (int) deltaX;
1539                } else {
1540                    awakenScrollBars();
1541                }
1542            } else if (mTouchState == TOUCH_STATE_REORDERING) {
1543                // Update the last motion position
1544                mLastMotionX = ev.getX();
1545                mLastMotionY = ev.getY();
1546
1547                // Update the parent down so that our zoom animations take this new movement into
1548                // account
1549                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1550                mParentDownMotionX = pt[0];
1551                mParentDownMotionY = pt[1];
1552                updateDragViewTranslationDuringDrag();
1553
1554                // Find the closest page to the touch point
1555                final int dragViewIndex = indexOfChild(mDragView);
1556                int bufferSize = (int) (REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE *
1557                    getViewportWidth());
1558                int leftBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.left, 0)[0]
1559                        + bufferSize);
1560                int rightBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.right, 0)[0]
1561                        - bufferSize);
1562
1563                // Change the drag view if we are hovering over the drop target
1564                boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
1565                        (int) mParentDownMotionX, (int) mParentDownMotionY);
1566                setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
1567
1568                if (DEBUG) Log.d(TAG, "leftBufferEdge: " + leftBufferEdge);
1569                if (DEBUG) Log.d(TAG, "rightBufferEdge: " + rightBufferEdge);
1570                if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
1571                if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
1572                if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
1573                if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
1574
1575                float parentX = mParentDownMotionX;
1576                int pageIndexToSnapTo = -1;
1577                if (parentX < leftBufferEdge && dragViewIndex > 0) {
1578                    pageIndexToSnapTo = dragViewIndex - 1;
1579                } else if (parentX > rightBufferEdge && dragViewIndex < getChildCount() - 1) {
1580                    pageIndexToSnapTo = dragViewIndex + 1;
1581                }
1582
1583                final int pageUnderPointIndex = pageIndexToSnapTo;
1584                if (pageUnderPointIndex > -1 && !isHoveringOverDelete) {
1585                    mTempVisiblePagesRange[0] = 0;
1586                    mTempVisiblePagesRange[1] = getPageCount() - 1;
1587                    boundByReorderablePages(true, mTempVisiblePagesRange);
1588                    if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
1589                            pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
1590                            pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
1591                        mSidePageHoverIndex = pageUnderPointIndex;
1592                        mSidePageHoverRunnable = new Runnable() {
1593                            @Override
1594                            public void run() {
1595                                // Update the down scroll position to account for the fact that the
1596                                // current page is moved
1597                                mDownScrollX = getChildOffset(pageUnderPointIndex)
1598                                        - getRelativeChildOffset(pageUnderPointIndex);
1599
1600                                // Setup the scroll to the correct page before we swap the views
1601                                snapToPage(pageUnderPointIndex);
1602
1603                                // For each of the pages between the paged view and the drag view,
1604                                // animate them from the previous position to the new position in
1605                                // the layout (as a result of the drag view moving in the layout)
1606                                int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
1607                                int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
1608                                        dragViewIndex + 1 : pageUnderPointIndex;
1609                                int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
1610                                        dragViewIndex - 1 : pageUnderPointIndex;
1611                                for (int i = lowerIndex; i <= upperIndex; ++i) {
1612                                    View v = getChildAt(i);
1613                                    // dragViewIndex < pageUnderPointIndex, so after we remove the
1614                                    // drag view all subsequent views to pageUnderPointIndex will
1615                                    // shift down.
1616                                    int oldX = getViewportOffsetX() + getChildOffset(i);
1617                                    int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
1618
1619                                    // Animate the view translation from its old position to its new
1620                                    // position
1621                                    AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY);
1622                                    if (anim != null) {
1623                                        anim.cancel();
1624                                    }
1625
1626                                    v.setTranslationX(oldX - newX);
1627                                    anim = new AnimatorSet();
1628                                    anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
1629                                    anim.playTogether(
1630                                            ObjectAnimator.ofFloat(v, "translationX", 0f));
1631                                    anim.start();
1632                                    v.setTag(anim);
1633                                }
1634
1635                                removeView(mDragView);
1636                                onRemoveView(mDragView, false);
1637                                addView(mDragView, pageUnderPointIndex);
1638                                onAddView(mDragView, pageUnderPointIndex);
1639                                mSidePageHoverIndex = -1;
1640                            }
1641                        };
1642                        postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
1643                    }
1644                } else {
1645                    removeCallbacks(mSidePageHoverRunnable);
1646                    mSidePageHoverIndex = -1;
1647                }
1648            } else {
1649                determineScrollingStart(ev);
1650            }
1651            break;
1652
1653        case MotionEvent.ACTION_UP:
1654            if (mTouchState == TOUCH_STATE_SCROLLING) {
1655                final int activePointerId = mActivePointerId;
1656                final int pointerIndex = ev.findPointerIndex(activePointerId);
1657                final float x = ev.getX(pointerIndex);
1658                final VelocityTracker velocityTracker = mVelocityTracker;
1659                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1660                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1661                final int deltaX = (int) (x - mDownMotionX);
1662                final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
1663                boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1664                        SIGNIFICANT_MOVE_THRESHOLD;
1665
1666                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1667
1668                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1669                        Math.abs(velocityX) > mFlingThresholdVelocity;
1670
1671                // In the case that the page is moved far to one direction and then is flung
1672                // in the opposite direction, we use a threshold to determine whether we should
1673                // just return to the starting page, or if we should skip one further.
1674                boolean returnToOriginalPage = false;
1675                if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1676                        Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1677                    returnToOriginalPage = true;
1678                }
1679
1680                int finalPage;
1681                // We give flings precedence over large moves, which is why we short-circuit our
1682                // test for a large move if a fling has been registered. That is, a large
1683                // move to the left and fling to the right will register as a fling to the right.
1684                final boolean isRtl = isLayoutRtl();
1685                boolean isDeltaXLeft = isRtl ? deltaX > 0 : deltaX < 0;
1686                boolean isVelocityXLeft = isRtl ? velocityX > 0 : velocityX < 0;
1687                if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
1688                        (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
1689                    finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
1690                    snapToPageWithVelocity(finalPage, velocityX);
1691                } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
1692                        (isFling && isVelocityXLeft)) &&
1693                        mCurrentPage < getChildCount() - 1) {
1694                    finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
1695                    snapToPageWithVelocity(finalPage, velocityX);
1696                } else {
1697                    snapToDestination();
1698                }            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1699                // at this point we have not moved beyond the touch slop
1700                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1701                // we can just page
1702                int nextPage = Math.max(0, mCurrentPage - 1);
1703                if (nextPage != mCurrentPage) {
1704                    snapToPage(nextPage);
1705                } else {
1706                    snapToDestination();
1707                }
1708            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1709                // at this point we have not moved beyond the touch slop
1710                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1711                // we can just page
1712                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1713                if (nextPage != mCurrentPage) {
1714                    snapToPage(nextPage);
1715                } else {
1716                    snapToDestination();
1717                }
1718            } else if (mTouchState == TOUCH_STATE_REORDERING) {
1719                // Update the last motion position
1720                mLastMotionX = ev.getX();
1721                mLastMotionY = ev.getY();
1722
1723                // Update the parent down so that our zoom animations take this new movement into
1724                // account
1725                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1726                mParentDownMotionX = pt[0];
1727                mParentDownMotionY = pt[1];
1728                updateDragViewTranslationDuringDrag();
1729                boolean handledFling = false;
1730                if (!DISABLE_FLING_TO_DELETE) {
1731                    // Check the velocity and see if we are flinging-to-delete
1732                    PointF flingToDeleteVector = isFlingingToDelete();
1733                    if (flingToDeleteVector != null) {
1734                        onFlingToDelete(flingToDeleteVector);
1735                        handledFling = true;
1736                    }
1737                }
1738                if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
1739                        (int) mParentDownMotionY)) {
1740                    onDropToDelete();
1741                }
1742            } else {
1743                onUnhandledTap(ev);
1744            }
1745
1746            // Remove the callback to wait for the side page hover timeout
1747            removeCallbacks(mSidePageHoverRunnable);
1748            // End any intermediate reordering states
1749            resetTouchState();
1750            break;
1751
1752        case MotionEvent.ACTION_CANCEL:
1753            if (mTouchState == TOUCH_STATE_SCROLLING) {
1754                snapToDestination();
1755            }
1756            resetTouchState();
1757            break;
1758
1759        case MotionEvent.ACTION_POINTER_UP:
1760            onSecondaryPointerUp(ev);
1761            break;
1762        }
1763
1764        return true;
1765    }
1766
1767    public void onFlingToDelete(View v) {}
1768    public void onRemoveView(View v, boolean deletePermanently) {}
1769    public void onRemoveViewAnimationCompleted() {}
1770    public void onAddView(View v, int index) {}
1771
1772    private void resetTouchState() {
1773        releaseVelocityTracker();
1774        endReordering();
1775        mTouchState = TOUCH_STATE_REST;
1776        mActivePointerId = INVALID_POINTER;
1777        mDownEventOnEdge = false;
1778    }
1779
1780    protected void onUnhandledTap(MotionEvent ev) {}
1781
1782    @Override
1783    public boolean onGenericMotionEvent(MotionEvent event) {
1784        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1785            switch (event.getAction()) {
1786                case MotionEvent.ACTION_SCROLL: {
1787                    // Handle mouse (or ext. device) by shifting the page depending on the scroll
1788                    final float vscroll;
1789                    final float hscroll;
1790                    if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1791                        vscroll = 0;
1792                        hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1793                    } else {
1794                        vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1795                        hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1796                    }
1797                    if (hscroll != 0 || vscroll != 0) {
1798                        boolean isForwardScroll = isLayoutRtl() ? (hscroll < 0 || vscroll < 0)
1799                                                         : (hscroll > 0 || vscroll > 0);
1800                        if (isForwardScroll) {
1801                            scrollRight();
1802                        } else {
1803                            scrollLeft();
1804                        }
1805                        return true;
1806                    }
1807                }
1808            }
1809        }
1810        return super.onGenericMotionEvent(event);
1811    }
1812
1813    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1814        if (mVelocityTracker == null) {
1815            mVelocityTracker = VelocityTracker.obtain();
1816        }
1817        mVelocityTracker.addMovement(ev);
1818    }
1819
1820    private void releaseVelocityTracker() {
1821        if (mVelocityTracker != null) {
1822            mVelocityTracker.recycle();
1823            mVelocityTracker = null;
1824        }
1825    }
1826
1827    private void onSecondaryPointerUp(MotionEvent ev) {
1828        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1829                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1830        final int pointerId = ev.getPointerId(pointerIndex);
1831        if (pointerId == mActivePointerId) {
1832            // This was our active pointer going up. Choose a new
1833            // active pointer and adjust accordingly.
1834            // TODO: Make this decision more intelligent.
1835            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1836            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1837            mLastMotionY = ev.getY(newPointerIndex);
1838            mLastMotionXRemainder = 0;
1839            mActivePointerId = ev.getPointerId(newPointerIndex);
1840            if (mVelocityTracker != null) {
1841                mVelocityTracker.clear();
1842            }
1843        }
1844    }
1845
1846    @Override
1847    public void requestChildFocus(View child, View focused) {
1848        super.requestChildFocus(child, focused);
1849        int page = indexToPage(indexOfChild(child));
1850        if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
1851            snapToPage(page);
1852        }
1853    }
1854
1855    protected int getChildWidth(int index) {
1856        return getPageAt(index).getMeasuredWidth();
1857    }
1858
1859    int getPageNearestToPoint(float x) {
1860        int index = 0;
1861        for (int i = 0; i < getChildCount(); ++i) {
1862            if (x < getChildAt(i).getRight() - getScrollX()) {
1863                return index;
1864            } else {
1865                index++;
1866            }
1867        }
1868        return Math.min(index, getChildCount() - 1);
1869    }
1870
1871    int getPageNearestToCenterOfScreen() {
1872        int minDistanceFromScreenCenter = Integer.MAX_VALUE;
1873        int minDistanceFromScreenCenterIndex = -1;
1874        int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
1875        final int childCount = getChildCount();
1876        for (int i = 0; i < childCount; ++i) {
1877            View layout = (View) getPageAt(i);
1878            int childWidth = layout.getMeasuredWidth();
1879            int halfChildWidth = (childWidth / 2);
1880            int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
1881            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1882            if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1883                minDistanceFromScreenCenter = distanceFromScreenCenter;
1884                minDistanceFromScreenCenterIndex = i;
1885            }
1886        }
1887        return minDistanceFromScreenCenterIndex;
1888    }
1889
1890    protected void snapToDestination() {
1891        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
1892    }
1893
1894    private static class ScrollInterpolator implements Interpolator {
1895        public ScrollInterpolator() {
1896        }
1897
1898        public float getInterpolation(float t) {
1899            t -= 1.0f;
1900            return t*t*t*t*t + 1;
1901        }
1902    }
1903
1904    // We want the duration of the page snap animation to be influenced by the distance that
1905    // the screen has to travel, however, we don't want this duration to be effected in a
1906    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1907    // of travel has on the overall snap duration.
1908    float distanceInfluenceForSnapDuration(float f) {
1909        f -= 0.5f; // center the values about 0.
1910        f *= 0.3f * Math.PI / 2.0f;
1911        return (float) Math.sin(f);
1912    }
1913
1914    protected void snapToPageWithVelocity(int whichPage, int velocity) {
1915        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
1916        int halfScreenSize = getViewportWidth() / 2;
1917
1918        if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1919        if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
1920                + getViewportWidth() + ", " + getChildWidth(whichPage));
1921        final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1922        int delta = newX - mUnboundedScrollX;
1923        int duration = 0;
1924
1925        if (Math.abs(velocity) < mMinFlingVelocity) {
1926            // If the velocity is low enough, then treat this more as an automatic page advance
1927            // as opposed to an apparent physical response to flinging
1928            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1929            return;
1930        }
1931
1932        // Here we compute a "distance" that will be used in the computation of the overall
1933        // snap duration. This is a function of the actual distance that needs to be traveled;
1934        // we keep this value close to half screen size in order to reduce the variance in snap
1935        // duration as a function of the distance the page needs to travel.
1936        float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
1937        float distance = halfScreenSize + halfScreenSize *
1938                distanceInfluenceForSnapDuration(distanceRatio);
1939
1940        velocity = Math.abs(velocity);
1941        velocity = Math.max(mMinSnapVelocity, velocity);
1942
1943        // we want the page's snap velocity to approximately match the velocity at which the
1944        // user flings, so we scale the duration by a value near to the derivative of the scroll
1945        // interpolator at zero, ie. 5. We use 4 to make it a little slower.
1946        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1947
1948        snapToPage(whichPage, delta, duration);
1949    }
1950
1951    protected void snapToPage(int whichPage) {
1952        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1953    }
1954
1955    protected void snapToPageImmediately(int whichPage) {
1956        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
1957    }
1958
1959    protected void snapToPage(int whichPage, int duration) {
1960        snapToPage(whichPage, duration, false);
1961    }
1962
1963    protected void snapToPage(int whichPage, int duration, boolean immediate) {
1964        whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1965
1966        if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1967        if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getViewportWidth() + ", "
1968                + getChildWidth(whichPage));
1969        int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1970        final int sX = mUnboundedScrollX;
1971        final int delta = newX - sX;
1972        snapToPage(whichPage, delta, duration, immediate);
1973    }
1974
1975    protected void snapToPage(int whichPage, int delta, int duration) {
1976        snapToPage(whichPage, delta, duration, false);
1977    }
1978
1979    protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
1980        mNextPage = whichPage;
1981        View focusedChild = getFocusedChild();
1982        if (focusedChild != null && whichPage != mCurrentPage &&
1983                focusedChild == getPageAt(mCurrentPage)) {
1984            focusedChild.clearFocus();
1985        }
1986
1987        pageBeginMoving();
1988        awakenScrollBars(duration);
1989        if (immediate) {
1990            duration = 0;
1991        } else if (duration == 0) {
1992            duration = Math.abs(delta);
1993        }
1994
1995        if (!mScroller.isFinished()) mScroller.abortAnimation();
1996        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
1997
1998        notifyPageSwitchListener();
1999
2000        // Trigger a compute() to finish switching pages if necessary
2001        if (immediate) {
2002            computeScroll();
2003        }
2004
2005        mForceScreenScrolled = true;
2006        invalidate();
2007    }
2008
2009    public void scrollLeft() {
2010        if (mScroller.isFinished()) {
2011            if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
2012        } else {
2013            if (mNextPage > 0) snapToPage(mNextPage - 1);
2014        }
2015    }
2016
2017    public void scrollRight() {
2018        if (mScroller.isFinished()) {
2019            if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
2020        } else {
2021            if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
2022        }
2023    }
2024
2025    public int getPageForView(View v) {
2026        int result = -1;
2027        if (v != null) {
2028            ViewParent vp = v.getParent();
2029            int count = getChildCount();
2030            for (int i = 0; i < count; i++) {
2031                if (vp == getPageAt(i)) {
2032                    return i;
2033                }
2034            }
2035        }
2036        return result;
2037    }
2038
2039    /**
2040     * @return True is long presses are still allowed for the current touch
2041     */
2042    public boolean allowLongPress() {
2043        return mAllowLongPress;
2044    }
2045
2046    /**
2047     * Set true to allow long-press events to be triggered, usually checked by
2048     * {@link Launcher} to accept or block dpad-initiated long-presses.
2049     */
2050    public void setAllowLongPress(boolean allowLongPress) {
2051        mAllowLongPress = allowLongPress;
2052    }
2053
2054    public static class SavedState extends BaseSavedState {
2055        int currentPage = -1;
2056
2057        SavedState(Parcelable superState) {
2058            super(superState);
2059        }
2060
2061        private SavedState(Parcel in) {
2062            super(in);
2063            currentPage = in.readInt();
2064        }
2065
2066        @Override
2067        public void writeToParcel(Parcel out, int flags) {
2068            super.writeToParcel(out, flags);
2069            out.writeInt(currentPage);
2070        }
2071
2072        public static final Parcelable.Creator<SavedState> CREATOR =
2073                new Parcelable.Creator<SavedState>() {
2074            public SavedState createFromParcel(Parcel in) {
2075                return new SavedState(in);
2076            }
2077
2078            public SavedState[] newArray(int size) {
2079                return new SavedState[size];
2080            }
2081        };
2082    }
2083
2084    protected void loadAssociatedPages(int page) {
2085        loadAssociatedPages(page, false);
2086    }
2087    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
2088        if (mContentIsRefreshable) {
2089            final int count = getChildCount();
2090            if (page < count) {
2091                int lowerPageBound = getAssociatedLowerPageBound(page);
2092                int upperPageBound = getAssociatedUpperPageBound(page);
2093                if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
2094                        + upperPageBound);
2095                // First, clear any pages that should no longer be loaded
2096                for (int i = 0; i < count; ++i) {
2097                    Page layout = (Page) getPageAt(i);
2098                    if ((i < lowerPageBound) || (i > upperPageBound)) {
2099                        if (layout.getPageChildCount() > 0) {
2100                            layout.removeAllViewsOnPage();
2101                        }
2102                        mDirtyPageContent.set(i, true);
2103                    }
2104                }
2105                // Next, load any new pages
2106                for (int i = 0; i < count; ++i) {
2107                    if ((i != page) && immediateAndOnly) {
2108                        continue;
2109                    }
2110                    if (lowerPageBound <= i && i <= upperPageBound) {
2111                        if (mDirtyPageContent.get(i)) {
2112                            syncPageItems(i, (i == page) && immediateAndOnly);
2113                            mDirtyPageContent.set(i, false);
2114                        }
2115                    }
2116                }
2117            }
2118        }
2119    }
2120
2121    protected int getAssociatedLowerPageBound(int page) {
2122        return Math.max(0, page - 1);
2123    }
2124    protected int getAssociatedUpperPageBound(int page) {
2125        final int count = getChildCount();
2126        return Math.min(page + 1, count - 1);
2127    }
2128
2129    /**
2130     * This method is called ONLY to synchronize the number of pages that the paged view has.
2131     * To actually fill the pages with information, implement syncPageItems() below.  It is
2132     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
2133     * and therefore, individual page items do not need to be updated in this method.
2134     */
2135    public abstract void syncPages();
2136
2137    /**
2138     * This method is called to synchronize the items that are on a particular page.  If views on
2139     * the page can be reused, then they should be updated within this method.
2140     */
2141    public abstract void syncPageItems(int page, boolean immediate);
2142
2143    protected void invalidatePageData() {
2144        invalidatePageData(-1, false);
2145    }
2146    protected void invalidatePageData(int currentPage) {
2147        invalidatePageData(currentPage, false);
2148    }
2149    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
2150        if (!mIsDataReady) {
2151            return;
2152        }
2153
2154        if (mContentIsRefreshable) {
2155            // Force all scrolling-related behavior to end
2156            mScroller.forceFinished(true);
2157            mNextPage = INVALID_PAGE;
2158
2159            // Update all the pages
2160            syncPages();
2161
2162            // We must force a measure after we've loaded the pages to update the content width and
2163            // to determine the full scroll width
2164            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
2165                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
2166
2167            // Set a new page as the current page if necessary
2168            if (currentPage > -1) {
2169                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
2170            }
2171
2172            // Mark each of the pages as dirty
2173            final int count = getChildCount();
2174            mDirtyPageContent.clear();
2175            for (int i = 0; i < count; ++i) {
2176                mDirtyPageContent.add(true);
2177            }
2178
2179            // Load any pages that are necessary for the current window of views
2180            loadAssociatedPages(mCurrentPage, immediateAndOnly);
2181            requestLayout();
2182        }
2183    }
2184
2185    protected View getScrollingIndicator() {
2186        // We use mHasScrollIndicator to prevent future lookups if there is no sibling indicator
2187        // found
2188        if (mHasScrollIndicator && mScrollIndicator == null) {
2189            ViewGroup parent = (ViewGroup) getParent();
2190            if (parent != null) {
2191                mScrollIndicator = (View) (parent.findViewById(R.id.paged_view_indicator));
2192                mHasScrollIndicator = mScrollIndicator != null;
2193                if (mHasScrollIndicator) {
2194                    mScrollIndicator.setVisibility(View.VISIBLE);
2195                }
2196            }
2197        }
2198        return mScrollIndicator;
2199    }
2200
2201    protected boolean isScrollingIndicatorEnabled() {
2202        return true;
2203    }
2204
2205    Runnable hideScrollingIndicatorRunnable = new Runnable() {
2206        @Override
2207        public void run() {
2208            hideScrollingIndicator(false);
2209        }
2210    };
2211
2212    protected void flashScrollingIndicator(boolean animated) {
2213        removeCallbacks(hideScrollingIndicatorRunnable);
2214        showScrollingIndicator(!animated);
2215        postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
2216    }
2217
2218    protected void showScrollingIndicator(boolean immediately) {
2219        mShouldShowScrollIndicator = true;
2220        mShouldShowScrollIndicatorImmediately = true;
2221        if (getChildCount() <= 1) return;
2222        if (!isScrollingIndicatorEnabled()) return;
2223
2224        mShouldShowScrollIndicator = false;
2225        getScrollingIndicator();
2226        if (mScrollIndicator != null) {
2227            // Fade the indicator in
2228            updateScrollingIndicatorPosition();
2229            mScrollIndicator.setVisibility(View.VISIBLE);
2230            cancelScrollingIndicatorAnimations();
2231            if (immediately) {
2232                mScrollIndicator.setAlpha(1f);
2233            } else {
2234                mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f);
2235                mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
2236                mScrollIndicatorAnimator.start();
2237            }
2238        }
2239    }
2240
2241    protected void cancelScrollingIndicatorAnimations() {
2242        if (mScrollIndicatorAnimator != null) {
2243            mScrollIndicatorAnimator.cancel();
2244        }
2245    }
2246
2247    protected void hideScrollingIndicator(boolean immediately) {
2248        if (getChildCount() <= 1) return;
2249        if (!isScrollingIndicatorEnabled()) return;
2250
2251        getScrollingIndicator();
2252        if (mScrollIndicator != null) {
2253            // Fade the indicator out
2254            updateScrollingIndicatorPosition();
2255            cancelScrollingIndicatorAnimations();
2256            if (immediately) {
2257                mScrollIndicator.setVisibility(View.INVISIBLE);
2258                mScrollIndicator.setAlpha(0f);
2259            } else {
2260                mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f);
2261                mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
2262                mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
2263                    private boolean cancelled = false;
2264                    @Override
2265                    public void onAnimationCancel(android.animation.Animator animation) {
2266                        cancelled = true;
2267                    }
2268                    @Override
2269                    public void onAnimationEnd(Animator animation) {
2270                        if (!cancelled) {
2271                            mScrollIndicator.setVisibility(View.INVISIBLE);
2272                        }
2273                    }
2274                });
2275                mScrollIndicatorAnimator.start();
2276            }
2277        }
2278    }
2279
2280    /**
2281     * To be overridden by subclasses to determine whether the scroll indicator should stretch to
2282     * fill its space on the track or not.
2283     */
2284    protected boolean hasElasticScrollIndicator() {
2285        return true;
2286    }
2287
2288    private void updateScrollingIndicator() {
2289        if (getChildCount() <= 1) return;
2290        if (!isScrollingIndicatorEnabled()) return;
2291
2292        getScrollingIndicator();
2293        if (mScrollIndicator != null) {
2294            updateScrollingIndicatorPosition();
2295        }
2296        if (mShouldShowScrollIndicator) {
2297            showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
2298        }
2299    }
2300
2301    private void updateScrollingIndicatorPosition() {
2302        final boolean isRtl = isLayoutRtl();
2303        if (!isScrollingIndicatorEnabled()) return;
2304        if (mScrollIndicator == null) return;
2305        int numPages = getChildCount();
2306        int pageWidth = getViewportWidth();
2307        int lastChildIndex = Math.max(0, getChildCount() - 1);
2308        int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
2309        int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
2310        int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
2311                mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
2312
2313        float scrollPos = isRtl ? mMaxScrollX - getScrollX() : getScrollX();
2314        float offset = Math.max(0f, Math.min(1f, (float) scrollPos / mMaxScrollX));
2315        if (isRtl) {
2316            offset = 1f - offset;
2317        }
2318        int indicatorSpace = trackWidth / numPages;
2319        int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
2320        if (hasElasticScrollIndicator()) {
2321            if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
2322                mScrollIndicator.getLayoutParams().width = indicatorSpace;
2323                mScrollIndicator.requestLayout();
2324            }
2325        } else {
2326            int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
2327            indicatorPos += indicatorCenterOffset;
2328        }
2329        mScrollIndicator.setTranslationX(indicatorPos);
2330    }
2331
2332    // Animate the drag view back to the original position
2333    void animateDragViewToOriginalPosition() {
2334        if (mDragView != null) {
2335            AnimatorSet anim = new AnimatorSet();
2336            anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
2337            anim.playTogether(
2338                    ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
2339                    ObjectAnimator.ofFloat(mDragView, "translationY", 0f));
2340            anim.addListener(new AnimatorListenerAdapter() {
2341                @Override
2342                public void onAnimationEnd(Animator animation) {
2343                    onPostReorderingAnimationCompleted();
2344                }
2345            });
2346            anim.start();
2347        }
2348    }
2349
2350    // "Zooms out" the PagedView to reveal more side pages
2351    protected boolean zoomOut() {
2352        if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2353            mZoomInOutAnim.cancel();
2354        }
2355
2356        if (!(getScaleX() < 1f || getScaleY() < 1f)) {
2357            mZoomInOutAnim = new AnimatorSet();
2358            mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2359            mZoomInOutAnim.playTogether(
2360                    ObjectAnimator.ofFloat(this, "scaleX", mMinScale),
2361                    ObjectAnimator.ofFloat(this, "scaleY", mMinScale));
2362            mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2363                @Override
2364                public void onAnimationStart(Animator animation) {
2365                    // Show the delete drop target
2366                    if (mDeleteDropTarget != null) {
2367                        mDeleteDropTarget.setVisibility(View.VISIBLE);
2368                        mDeleteDropTarget.animate().alpha(1f)
2369                            .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2370                            .setListener(new AnimatorListenerAdapter() {
2371                                @Override
2372                                public void onAnimationStart(Animator animation) {
2373                                    mDeleteDropTarget.setAlpha(0f);
2374                                }
2375                            });
2376                    }
2377                }
2378            });
2379            mZoomInOutAnim.start();
2380            return true;
2381        }
2382        return false;
2383    }
2384
2385    protected void onStartReordering() {
2386        // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
2387        mTouchState = TOUCH_STATE_REORDERING;
2388        mIsReordering = true;
2389
2390        // Mark all the non-widget pages as invisible
2391        getVisiblePages(mTempVisiblePagesRange);
2392        boundByReorderablePages(true, mTempVisiblePagesRange);
2393        for (int i = 0; i < getPageCount(); ++i) {
2394            if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2395                getPageAt(i).setAlpha(0f);
2396            }
2397        }
2398
2399        // We must invalidate to trigger a redraw to update the layers such that the drag view
2400        // is always drawn on top
2401        invalidate();
2402    }
2403
2404    private void onPostReorderingAnimationCompleted() {
2405        // Trigger the callback when reordering has settled
2406        --mPostReorderingPreZoomInRemainingAnimationCount;
2407        if (mPostReorderingPreZoomInRunnable != null &&
2408                mPostReorderingPreZoomInRemainingAnimationCount == 0) {
2409            mPostReorderingPreZoomInRunnable.run();
2410            mPostReorderingPreZoomInRunnable = null;
2411        }
2412    }
2413
2414    protected void onEndReordering() {
2415        mIsReordering = false;
2416
2417        // Mark all the non-widget pages as visible again
2418        getVisiblePages(mTempVisiblePagesRange);
2419        boundByReorderablePages(true, mTempVisiblePagesRange);
2420        for (int i = 0; i < getPageCount(); ++i) {
2421            if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2422                getPageAt(i).setAlpha(1f);
2423            }
2424        }
2425    }
2426
2427    public boolean startReordering() {
2428        int dragViewIndex = getPageNearestToCenterOfScreen();
2429        mTempVisiblePagesRange[0] = 0;
2430        mTempVisiblePagesRange[1] = getPageCount() - 1;
2431        boundByReorderablePages(true, mTempVisiblePagesRange);
2432        mReorderingStarted = true;
2433
2434        // Check if we are within the reordering range
2435        if (mTempVisiblePagesRange[0] <= dragViewIndex &&
2436                dragViewIndex <= mTempVisiblePagesRange[1]) {
2437            if (zoomOut()) {
2438                // Find the drag view under the pointer
2439                mDragView = getChildAt(dragViewIndex);
2440
2441                onStartReordering();
2442            }
2443            return true;
2444        }
2445        return false;
2446    }
2447
2448    boolean isReordering(boolean testTouchState) {
2449        boolean state = mIsReordering;
2450        if (testTouchState) {
2451            state &= (mTouchState == TOUCH_STATE_REORDERING);
2452        }
2453        return state;
2454    }
2455    void endReordering() {
2456        // For simplicity, we call endReordering sometimes even if reordering was never started.
2457        // In that case, we don't want to do anything.
2458        if (!mReorderingStarted) return;
2459        mReorderingStarted = false;
2460
2461        // If we haven't flung-to-delete the current child, then we just animate the drag view
2462        // back into position
2463        final Runnable onCompleteRunnable = new Runnable() {
2464            @Override
2465            public void run() {
2466                onEndReordering();
2467            }
2468        };
2469        if (!mDeferringForDelete) {
2470            mPostReorderingPreZoomInRunnable = new Runnable() {
2471                public void run() {
2472                    zoomIn(onCompleteRunnable);
2473                };
2474            };
2475
2476            mPostReorderingPreZoomInRemainingAnimationCount =
2477                    NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
2478            // Snap to the current page
2479            snapToPage(indexOfChild(mDragView), 0);
2480            // Animate the drag view back to the front position
2481            animateDragViewToOriginalPosition();
2482        } else {
2483            // Handled in post-delete-animation-callbacks
2484        }
2485    }
2486
2487    // "Zooms in" the PagedView to highlight the current page
2488    protected boolean zoomIn(final Runnable onCompleteRunnable) {
2489        if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2490            mZoomInOutAnim.cancel();
2491        }
2492        if (getScaleX() < 1f || getScaleY() < 1f) {
2493            mZoomInOutAnim = new AnimatorSet();
2494            mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2495            mZoomInOutAnim.playTogether(
2496                    ObjectAnimator.ofFloat(this, "scaleX", 1f),
2497                    ObjectAnimator.ofFloat(this, "scaleY", 1f));
2498            mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2499                @Override
2500                public void onAnimationStart(Animator animation) {
2501                    // Hide the delete drop target
2502                    if (mDeleteDropTarget != null) {
2503                        mDeleteDropTarget.animate().alpha(0f)
2504                            .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2505                            .setListener(new AnimatorListenerAdapter() {
2506                                @Override
2507                                public void onAnimationEnd(Animator animation) {
2508                                    mDeleteDropTarget.setVisibility(View.GONE);
2509                                }
2510                            });
2511                    }
2512                }
2513                @Override
2514                public void onAnimationCancel(Animator animation) {
2515                    mDragView = null;
2516                }
2517                @Override
2518                public void onAnimationEnd(Animator animation) {
2519                    mDragView = null;
2520                    if (onCompleteRunnable != null) {
2521                        onCompleteRunnable.run();
2522                    }
2523                }
2524            });
2525            mZoomInOutAnim.start();
2526            return true;
2527        } else {
2528            if (onCompleteRunnable != null) {
2529                onCompleteRunnable.run();
2530            }
2531        }
2532        return false;
2533    }
2534
2535    /*
2536     * Flinging to delete - IN PROGRESS
2537     */
2538    private PointF isFlingingToDelete() {
2539        ViewConfiguration config = ViewConfiguration.get(getContext());
2540        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
2541
2542        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
2543            // Do a quick dot product test to ensure that we are flinging upwards
2544            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
2545                    mVelocityTracker.getYVelocity());
2546            PointF upVec = new PointF(0f, -1f);
2547            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
2548                    (vel.length() * upVec.length()));
2549            if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
2550                return vel;
2551            }
2552        }
2553        return null;
2554    }
2555
2556    /**
2557     * Creates an animation from the current drag view along its current velocity vector.
2558     * For this animation, the alpha runs for a fixed duration and we update the position
2559     * progressively.
2560     */
2561    private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
2562        private View mDragView;
2563        private PointF mVelocity;
2564        private Rect mFrom;
2565        private long mPrevTime;
2566        private float mFriction;
2567
2568        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
2569
2570        public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
2571                long startTime, float friction) {
2572            mDragView = dragView;
2573            mVelocity = vel;
2574            mFrom = from;
2575            mPrevTime = startTime;
2576            mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
2577        }
2578
2579        @Override
2580        public void onAnimationUpdate(ValueAnimator animation) {
2581            float t = ((Float) animation.getAnimatedValue()).floatValue();
2582            long curTime = AnimationUtils.currentAnimationTimeMillis();
2583
2584            mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
2585            mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
2586
2587            mDragView.setTranslationX(mFrom.left);
2588            mDragView.setTranslationY(mFrom.top);
2589            mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
2590
2591            mVelocity.x *= mFriction;
2592            mVelocity.y *= mFriction;
2593            mPrevTime = curTime;
2594        }
2595    };
2596
2597    private static final int ANIM_TAG_KEY = 100;
2598
2599    private Runnable createPostDeleteAnimationRunnable(final View dragView) {
2600        return new Runnable() {
2601            @Override
2602            public void run() {
2603                int dragViewIndex = indexOfChild(dragView);
2604
2605                // For each of the pages around the drag view, animate them from the previous
2606                // position to the new position in the layout (as a result of the drag view moving
2607                // in the layout)
2608                // NOTE: We can make an assumption here because we have side-bound pages that we
2609                //       will always have pages to animate in from the left
2610                getVisiblePages(mTempVisiblePagesRange);
2611                boundByReorderablePages(true, mTempVisiblePagesRange);
2612                boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
2613                boolean slideFromLeft = (isLastWidgetPage ||
2614                        dragViewIndex > mTempVisiblePagesRange[0]);
2615
2616                // Setup the scroll to the correct page before we swap the views
2617                if (slideFromLeft) {
2618                    snapToPageImmediately(dragViewIndex - 1);
2619                }
2620
2621                int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
2622                int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
2623                int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
2624                int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
2625                ArrayList<Animator> animations = new ArrayList<Animator>();
2626                for (int i = lowerIndex; i <= upperIndex; ++i) {
2627                    View v = getChildAt(i);
2628                    // dragViewIndex < pageUnderPointIndex, so after we remove the
2629                    // drag view all subsequent views to pageUnderPointIndex will
2630                    // shift down.
2631                    int oldX = 0;
2632                    int newX = 0;
2633                    if (slideFromLeft) {
2634                        if (i == 0) {
2635                            // Simulate the page being offscreen with the page spacing
2636                            oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
2637                                    - mPageSpacing;
2638                        } else {
2639                            oldX = getViewportOffsetX() + getChildOffset(i - 1);
2640                        }
2641                        newX = getViewportOffsetX() + getChildOffset(i);
2642                    } else {
2643                        oldX = getChildOffset(i) - getChildOffset(i - 1);
2644                        newX = 0;
2645                    }
2646
2647                    // Animate the view translation from its old position to its new
2648                    // position
2649                    AnimatorSet anim = (AnimatorSet) v.getTag();
2650                    if (anim != null) {
2651                        anim.cancel();
2652                    }
2653
2654                    // Note: Hacky, but we want to skip any optimizations to not draw completely
2655                    // hidden views
2656                    v.setAlpha(Math.max(v.getAlpha(), 0.01f));
2657                    v.setTranslationX(oldX - newX);
2658                    anim = new AnimatorSet();
2659                    anim.playTogether(
2660                            ObjectAnimator.ofFloat(v, "translationX", 0f),
2661                            ObjectAnimator.ofFloat(v, "alpha", 1f));
2662                    animations.add(anim);
2663                    v.setTag(ANIM_TAG_KEY, anim);
2664                }
2665
2666                AnimatorSet slideAnimations = new AnimatorSet();
2667                slideAnimations.playTogether(animations);
2668                slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
2669                slideAnimations.addListener(new AnimatorListenerAdapter() {
2670                    @Override
2671                    public void onAnimationEnd(Animator animation) {
2672                        final Runnable onCompleteRunnable = new Runnable() {
2673                            @Override
2674                            public void run() {
2675                                mDeferringForDelete = false;
2676                                onEndReordering();
2677                                onRemoveViewAnimationCompleted();
2678                            }
2679                        };
2680                        zoomIn(onCompleteRunnable);
2681                    }
2682                });
2683                slideAnimations.start();
2684
2685                removeView(dragView);
2686                onRemoveView(dragView, true);
2687            }
2688        };
2689    }
2690
2691    public void onFlingToDelete(PointF vel) {
2692        final long startTime = AnimationUtils.currentAnimationTimeMillis();
2693
2694        // NOTE: Because it takes time for the first frame of animation to actually be
2695        // called and we expect the animation to be a continuation of the fling, we have
2696        // to account for the time that has elapsed since the fling finished.  And since
2697        // we don't have a startDelay, we will always get call to update when we call
2698        // start() (which we want to ignore).
2699        final TimeInterpolator tInterpolator = new TimeInterpolator() {
2700            private int mCount = -1;
2701            private long mStartTime;
2702            private float mOffset;
2703            /* Anonymous inner class ctor */ {
2704                mStartTime = startTime;
2705            }
2706
2707            @Override
2708            public float getInterpolation(float t) {
2709                if (mCount < 0) {
2710                    mCount++;
2711                } else if (mCount == 0) {
2712                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
2713                            mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
2714                    mCount++;
2715                }
2716                return Math.min(1f, mOffset + t);
2717            }
2718        };
2719
2720        final Rect from = new Rect();
2721        final View dragView = mDragView;
2722        from.left = (int) dragView.getTranslationX();
2723        from.top = (int) dragView.getTranslationY();
2724        AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
2725                from, startTime, FLING_TO_DELETE_FRICTION);
2726
2727        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2728
2729        // Create and start the animation
2730        ValueAnimator mDropAnim = new ValueAnimator();
2731        mDropAnim.setInterpolator(tInterpolator);
2732        mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
2733        mDropAnim.setFloatValues(0f, 1f);
2734        mDropAnim.addUpdateListener(updateCb);
2735        mDropAnim.addListener(new AnimatorListenerAdapter() {
2736            public void onAnimationEnd(Animator animation) {
2737                onAnimationEndRunnable.run();
2738            }
2739        });
2740        mDropAnim.start();
2741        mDeferringForDelete = true;
2742    }
2743
2744    /* Drag to delete */
2745    private boolean isHoveringOverDeleteDropTarget(int x, int y) {
2746        if (mDeleteDropTarget != null) {
2747            mAltTmpRect.set(0, 0, 0, 0);
2748            View parent = (View) mDeleteDropTarget.getParent();
2749            if (parent != null) {
2750                parent.getGlobalVisibleRect(mAltTmpRect);
2751            }
2752            mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
2753            mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
2754            return mTmpRect.contains(x, y);
2755        }
2756        return false;
2757    }
2758
2759    protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
2760
2761    private void onDropToDelete() {
2762        final View dragView = mDragView;
2763
2764        final float toScale = 0f;
2765        final float toAlpha = 0f;
2766
2767        // Create and start the complex animation
2768        ArrayList<Animator> animations = new ArrayList<Animator>();
2769        AnimatorSet motionAnim = new AnimatorSet();
2770        motionAnim.setInterpolator(new DecelerateInterpolator(2));
2771        motionAnim.playTogether(
2772                ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
2773                ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
2774        animations.add(motionAnim);
2775
2776        AnimatorSet alphaAnim = new AnimatorSet();
2777        alphaAnim.setInterpolator(new LinearInterpolator());
2778        alphaAnim.playTogether(
2779                ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
2780        animations.add(alphaAnim);
2781
2782        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2783
2784        AnimatorSet anim = new AnimatorSet();
2785        anim.playTogether(animations);
2786        anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
2787        anim.addListener(new AnimatorListenerAdapter() {
2788            public void onAnimationEnd(Animator animation) {
2789                onAnimationEndRunnable.run();
2790            }
2791        });
2792        anim.start();
2793
2794        mDeferringForDelete = true;
2795    }
2796
2797    /* Accessibility */
2798    @Override
2799    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2800        super.onInitializeAccessibilityNodeInfo(info);
2801        info.setScrollable(getPageCount() > 1);
2802        if (getCurrentPage() < getPageCount() - 1) {
2803            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2804        }
2805        if (getCurrentPage() > 0) {
2806            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2807        }
2808    }
2809
2810    @Override
2811    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2812        super.onInitializeAccessibilityEvent(event);
2813        event.setScrollable(true);
2814        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2815            event.setFromIndex(mCurrentPage);
2816            event.setToIndex(mCurrentPage);
2817            event.setItemCount(getChildCount());
2818        }
2819    }
2820
2821    @Override
2822    public boolean performAccessibilityAction(int action, Bundle arguments) {
2823        if (super.performAccessibilityAction(action, arguments)) {
2824            return true;
2825        }
2826        switch (action) {
2827            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2828                if (getCurrentPage() < getPageCount() - 1) {
2829                    scrollRight();
2830                    return true;
2831                }
2832            } break;
2833            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2834                if (getCurrentPage() > 0) {
2835                    scrollLeft();
2836                    return true;
2837                }
2838            } break;
2839        }
2840        return false;
2841    }
2842
2843    protected String getCurrentPageDescription() {
2844        return String.format(getContext().getString(R.string.default_scroll_format),
2845                getNextPage() + 1, getChildCount());
2846    }
2847
2848    @Override
2849    public boolean onHoverEvent(android.view.MotionEvent event) {
2850        return true;
2851    }
2852}
2853