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