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