PagedView.java revision c82d2622bc7305fd3545fe7df97a30187283113f
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                if (finishedScrolling) {
1372                    mTouchState = TOUCH_STATE_REST;
1373                    mScrollAbortedFromIntercept = true;
1374                    abortScrollerAnimation(false);
1375                } else {
1376                    if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
1377                        mTouchState = TOUCH_STATE_SCROLLING;
1378                    } else {
1379                        mTouchState = TOUCH_STATE_REST;
1380                    }
1381                }
1382
1383                // check if this can be the beginning of a tap on the side of the pages
1384                // to scroll the current page
1385                if (!DISABLE_TOUCH_SIDE_PAGES) {
1386                    if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
1387                        if (getChildCount() > 0) {
1388                            if (hitsPreviousPage(x, y)) {
1389                                mTouchState = TOUCH_STATE_PREV_PAGE;
1390                            } else if (hitsNextPage(x, y)) {
1391                                mTouchState = TOUCH_STATE_NEXT_PAGE;
1392                            }
1393                        }
1394                    }
1395                }
1396                break;
1397            }
1398
1399            case MotionEvent.ACTION_UP:
1400            case MotionEvent.ACTION_CANCEL:
1401                if (mScrollAbortedFromIntercept) {
1402                    snapToDestination();
1403                }
1404                resetTouchState();
1405                break;
1406
1407            case MotionEvent.ACTION_POINTER_UP:
1408                onSecondaryPointerUp(ev);
1409                releaseVelocityTracker();
1410                break;
1411        }
1412
1413        /*
1414         * The only time we want to intercept motion events is if we are in the
1415         * drag mode.
1416         */
1417        return mTouchState != TOUCH_STATE_REST;
1418    }
1419
1420    protected void determineScrollingStart(MotionEvent ev) {
1421        determineScrollingStart(ev, 1.0f);
1422    }
1423
1424    /*
1425     * Determines if we should change the touch state to start scrolling after the
1426     * user moves their touch point too far.
1427     */
1428    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1429        // Disallow scrolling if we don't have a valid pointer index
1430        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1431        if (pointerIndex == -1) return;
1432
1433        // Disallow scrolling if we started the gesture from outside the viewport
1434        final float x = ev.getX(pointerIndex);
1435        final float y = ev.getY(pointerIndex);
1436        if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
1437
1438        final int xDiff = (int) Math.abs(x - mLastMotionX);
1439        final int yDiff = (int) Math.abs(y - mLastMotionY);
1440
1441        final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
1442        boolean xPaged = xDiff > mPagingTouchSlop;
1443        boolean xMoved = xDiff > touchSlop;
1444        boolean yMoved = yDiff > touchSlop;
1445
1446        if (xMoved || xPaged || yMoved) {
1447            if (mUsePagingTouchSlop ? xPaged : xMoved) {
1448                // Scroll if the user moved far enough along the X axis
1449                mTouchState = TOUCH_STATE_SCROLLING;
1450                mTotalMotionX += Math.abs(mLastMotionX - x);
1451                mLastMotionX = x;
1452                mLastMotionXRemainder = 0;
1453                mTouchX = getViewportOffsetX() + getScrollX();
1454                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1455                pageBeginMoving();
1456            }
1457        }
1458    }
1459
1460    protected float getMaxScrollProgress() {
1461        return 1.0f;
1462    }
1463
1464    protected void cancelCurrentPageLongPress() {
1465        if (mAllowLongPress) {
1466            //mAllowLongPress = false;
1467            // Try canceling the long press. It could also have been scheduled
1468            // by a distant descendant, so use the mAllowLongPress flag to block
1469            // everything
1470            final View currentPage = getPageAt(mCurrentPage);
1471            if (currentPage != null) {
1472                currentPage.cancelLongPress();
1473            }
1474        }
1475    }
1476
1477    protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
1478        final int halfScreenSize = getViewportWidth() / 2;
1479
1480        screenCenter = Math.min(getScrollX() + halfScreenSize, screenCenter);
1481        screenCenter = Math.max(halfScreenSize,  screenCenter);
1482
1483        return getScrollProgress(screenCenter, v, page);
1484    }
1485
1486    protected float getScrollProgress(int screenCenter, View v, int page) {
1487        final int halfScreenSize = getViewportWidth() / 2;
1488
1489        int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
1490        int count = getChildCount();
1491
1492        final int totalDistance;
1493
1494        int adjacentPage = page + 1;
1495        if ((delta < 0 && !isLayoutRtl()) || (delta > 0 && isLayoutRtl())) {
1496            adjacentPage = page - 1;
1497        }
1498
1499        if (adjacentPage < 0 || adjacentPage > count - 1) {
1500            totalDistance = v.getMeasuredWidth() + mPageSpacing;
1501        } else {
1502            totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page));
1503        }
1504
1505        float scrollProgress = delta / (totalDistance * 1.0f);
1506        scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
1507        scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
1508        return scrollProgress;
1509    }
1510
1511    public int getScrollForPage(int index) {
1512        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
1513            return 0;
1514        } else {
1515            return mPageScrolls[index];
1516        }
1517    }
1518
1519    // While layout transitions are occurring, a child's position may stray from its baseline
1520    // position. This method returns the magnitude of this stray at any given time.
1521    public int getLayoutTransitionOffsetForPage(int index) {
1522        if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
1523            return 0;
1524        } else {
1525            View child = getChildAt(index);
1526
1527            int scrollOffset = 0;
1528            LayoutParams lp = (LayoutParams) child.getLayoutParams();
1529            if (!lp.isFullScreenPage) {
1530                scrollOffset = isLayoutRtl() ? getPaddingRight() : getPaddingLeft();
1531            }
1532
1533            int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
1534            return (int) (child.getX() - baselineX);
1535        }
1536    }
1537
1538    // This curve determines how the effect of scrolling over the limits of the page dimishes
1539    // as the user pulls further and further from the bounds
1540    private float overScrollInfluenceCurve(float f) {
1541        f -= 1.0f;
1542        return f * f * f + 1.0f;
1543    }
1544
1545    protected void acceleratedOverScroll(float amount) {
1546        int screenSize = getViewportWidth();
1547
1548        // We want to reach the max over scroll effect when the user has
1549        // over scrolled half the size of the screen
1550        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1551
1552        if (f == 0) return;
1553
1554        // Clamp this factor, f, to -1 < f < 1
1555        if (Math.abs(f) >= 1) {
1556            f /= Math.abs(f);
1557        }
1558
1559        int overScrollAmount = (int) Math.round(f * screenSize);
1560        if (amount < 0) {
1561            mOverScrollX = overScrollAmount;
1562            super.scrollTo(0, getScrollY());
1563        } else {
1564            mOverScrollX = mMaxScrollX + overScrollAmount;
1565            super.scrollTo(mMaxScrollX, getScrollY());
1566        }
1567        invalidate();
1568    }
1569
1570    protected void dampedOverScroll(float amount) {
1571        int screenSize = getViewportWidth();
1572
1573        float f = (amount / screenSize);
1574
1575        if (f == 0) return;
1576        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1577
1578        // Clamp this factor, f, to -1 < f < 1
1579        if (Math.abs(f) >= 1) {
1580            f /= Math.abs(f);
1581        }
1582
1583        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
1584        if (amount < 0) {
1585            mOverScrollX = overScrollAmount;
1586            super.scrollTo(0, getScrollY());
1587        } else {
1588            mOverScrollX = mMaxScrollX + overScrollAmount;
1589            super.scrollTo(mMaxScrollX, getScrollY());
1590        }
1591        invalidate();
1592    }
1593
1594    protected void overScroll(float amount) {
1595        dampedOverScroll(amount);
1596    }
1597
1598    protected float maxOverScroll() {
1599        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1600        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1601        float f = 1.0f;
1602        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1603        return OVERSCROLL_DAMP_FACTOR * f;
1604    }
1605
1606    protected void enableFreeScroll() {
1607        setEnableFreeScroll(true, -1);
1608    }
1609
1610    protected void disableFreeScroll(int snapPage) {
1611        setEnableFreeScroll(false, snapPage);
1612    }
1613
1614    void updateFreescrollBounds() {
1615        getOverviewModePages(mTempVisiblePagesRange);
1616        if (isLayoutRtl()) {
1617            mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
1618            mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
1619        } else {
1620            mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
1621            mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
1622        }
1623    }
1624
1625    private void setEnableFreeScroll(boolean freeScroll, int snapPage) {
1626        mFreeScroll = freeScroll;
1627
1628        if (snapPage == -1) {
1629            snapPage = getPageNearestToCenterOfScreen();
1630        }
1631
1632        if (!mFreeScroll) {
1633            snapToPage(snapPage);
1634        } else {
1635            updateFreescrollBounds();
1636            getOverviewModePages(mTempVisiblePagesRange);
1637            if (getCurrentPage() < mTempVisiblePagesRange[0]) {
1638                setCurrentPage(mTempVisiblePagesRange[0]);
1639            } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
1640                setCurrentPage(mTempVisiblePagesRange[1]);
1641            }
1642        }
1643
1644        setEnableOverscroll(!freeScroll);
1645    }
1646
1647    private void setEnableOverscroll(boolean enable) {
1648        mAllowOverScroll = enable;
1649    }
1650
1651    int getNearestHoverOverPageIndex() {
1652        if (mDragView != null) {
1653            int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
1654                    + mDragView.getTranslationX());
1655            getOverviewModePages(mTempVisiblePagesRange);
1656            int minDistance = Integer.MAX_VALUE;
1657            int minIndex = indexOfChild(mDragView);
1658            for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
1659                View page = getPageAt(i);
1660                int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
1661                int d = Math.abs(dragX - pageX);
1662                if (d < minDistance) {
1663                    minIndex = i;
1664                    minDistance = d;
1665                }
1666            }
1667            return minIndex;
1668        }
1669        return -1;
1670    }
1671
1672    @Override
1673    public boolean onTouchEvent(MotionEvent ev) {
1674        if (DISABLE_TOUCH_INTERACTION) {
1675            return false;
1676        }
1677
1678        super.onTouchEvent(ev);
1679
1680        // Skip touch handling if there are no pages to swipe
1681        if (getChildCount() <= 0) return super.onTouchEvent(ev);
1682
1683        acquireVelocityTrackerAndAddMovement(ev);
1684
1685        final int action = ev.getAction();
1686
1687        switch (action & MotionEvent.ACTION_MASK) {
1688        case MotionEvent.ACTION_DOWN:
1689            /*
1690             * If being flinged and user touches, stop the fling. isFinished
1691             * will be false if being flinged.
1692             */
1693            if (!mScroller.isFinished()) {
1694                abortScrollerAnimation(false);
1695            }
1696
1697            // Remember where the motion event started
1698            mDownMotionX = mLastMotionX = ev.getX();
1699            mDownMotionY = mLastMotionY = ev.getY();
1700            mDownScrollX = getScrollX();
1701            float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1702            mParentDownMotionX = p[0];
1703            mParentDownMotionY = p[1];
1704            mLastMotionXRemainder = 0;
1705            mTotalMotionX = 0;
1706            mActivePointerId = ev.getPointerId(0);
1707
1708            if (mTouchState == TOUCH_STATE_SCROLLING) {
1709                pageBeginMoving();
1710            }
1711            break;
1712
1713        case MotionEvent.ACTION_MOVE:
1714            if (mTouchState == TOUCH_STATE_SCROLLING) {
1715                // Scroll to follow the motion event
1716                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1717
1718                if (pointerIndex == -1) return true;
1719
1720                final float x = ev.getX(pointerIndex);
1721                final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1722
1723                mTotalMotionX += Math.abs(deltaX);
1724
1725                // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
1726                // keep the remainder because we are actually testing if we've moved from the last
1727                // scrolled position (which is discrete).
1728                if (Math.abs(deltaX) >= 1.0f) {
1729                    mTouchX += deltaX;
1730                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1731                    if (!mDeferScrollUpdate) {
1732                        scrollBy((int) deltaX, 0);
1733                        if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
1734                    } else {
1735                        invalidate();
1736                    }
1737                    mLastMotionX = x;
1738                    mLastMotionXRemainder = deltaX - (int) deltaX;
1739                } else {
1740                    awakenScrollBars();
1741                }
1742            } else if (mTouchState == TOUCH_STATE_REORDERING) {
1743                // Update the last motion position
1744                mLastMotionX = ev.getX();
1745                mLastMotionY = ev.getY();
1746
1747                // Update the parent down so that our zoom animations take this new movement into
1748                // account
1749                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1750                mParentDownMotionX = pt[0];
1751                mParentDownMotionY = pt[1];
1752                updateDragViewTranslationDuringDrag();
1753
1754                // Find the closest page to the touch point
1755                final int dragViewIndex = indexOfChild(mDragView);
1756
1757                // Change the drag view if we are hovering over the drop target
1758                boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
1759                        (int) mParentDownMotionX, (int) mParentDownMotionY);
1760                setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
1761
1762                if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
1763                if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
1764                if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
1765                if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
1766
1767                final int pageUnderPointIndex = getNearestHoverOverPageIndex();
1768                if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView) &&
1769                        !isHoveringOverDelete) {
1770                    mTempVisiblePagesRange[0] = 0;
1771                    mTempVisiblePagesRange[1] = getPageCount() - 1;
1772                    getOverviewModePages(mTempVisiblePagesRange);
1773                    if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
1774                            pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
1775                            pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
1776                        mSidePageHoverIndex = pageUnderPointIndex;
1777                        mSidePageHoverRunnable = new Runnable() {
1778                            @Override
1779                            public void run() {
1780                                // Setup the scroll to the correct page before we swap the views
1781                                snapToPage(pageUnderPointIndex);
1782
1783                                // For each of the pages between the paged view and the drag view,
1784                                // animate them from the previous position to the new position in
1785                                // the layout (as a result of the drag view moving in the layout)
1786                                int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
1787                                int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
1788                                        dragViewIndex + 1 : pageUnderPointIndex;
1789                                int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
1790                                        dragViewIndex - 1 : pageUnderPointIndex;
1791                                for (int i = lowerIndex; i <= upperIndex; ++i) {
1792                                    View v = getChildAt(i);
1793                                    // dragViewIndex < pageUnderPointIndex, so after we remove the
1794                                    // drag view all subsequent views to pageUnderPointIndex will
1795                                    // shift down.
1796                                    int oldX = getViewportOffsetX() + getChildOffset(i);
1797                                    int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
1798
1799                                    // Animate the view translation from its old position to its new
1800                                    // position
1801                                    AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY);
1802                                    if (anim != null) {
1803                                        anim.cancel();
1804                                    }
1805
1806                                    v.setTranslationX(oldX - newX);
1807                                    anim = new AnimatorSet();
1808                                    anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
1809                                    anim.playTogether(
1810                                            ObjectAnimator.ofFloat(v, "translationX", 0f));
1811                                    anim.start();
1812                                    v.setTag(anim);
1813                                }
1814
1815                                removeView(mDragView);
1816                                onRemoveView(mDragView, false);
1817                                addView(mDragView, pageUnderPointIndex);
1818                                onAddView(mDragView, pageUnderPointIndex);
1819                                mSidePageHoverIndex = -1;
1820                                mPageIndicator.setActiveMarker(getNextPage());
1821                            }
1822                        };
1823                        postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
1824                    }
1825                } else {
1826                    removeCallbacks(mSidePageHoverRunnable);
1827                    mSidePageHoverIndex = -1;
1828                }
1829            } else {
1830                determineScrollingStart(ev);
1831            }
1832            break;
1833
1834        case MotionEvent.ACTION_UP:
1835            if (mTouchState == TOUCH_STATE_SCROLLING) {
1836                final int activePointerId = mActivePointerId;
1837                final int pointerIndex = ev.findPointerIndex(activePointerId);
1838                final float x = ev.getX(pointerIndex);
1839                final VelocityTracker velocityTracker = mVelocityTracker;
1840                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1841                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1842                final int deltaX = (int) (x - mDownMotionX);
1843                final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
1844                boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1845                        SIGNIFICANT_MOVE_THRESHOLD;
1846
1847                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1848
1849                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1850                        Math.abs(velocityX) > mFlingThresholdVelocity;
1851
1852                if (!mFreeScroll) {
1853                    // In the case that the page is moved far to one direction and then is flung
1854                    // in the opposite direction, we use a threshold to determine whether we should
1855                    // just return to the starting page, or if we should skip one further.
1856                    boolean returnToOriginalPage = false;
1857                    if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1858                            Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1859                        returnToOriginalPage = true;
1860                    }
1861
1862                    int finalPage;
1863                    // We give flings precedence over large moves, which is why we short-circuit our
1864                    // test for a large move if a fling has been registered. That is, a large
1865                    // move to the left and fling to the right will register as a fling to the right.
1866                    final boolean isRtl = isLayoutRtl();
1867                    boolean isDeltaXLeft = isRtl ? deltaX > 0 : deltaX < 0;
1868                    boolean isVelocityXLeft = isRtl ? velocityX > 0 : velocityX < 0;
1869                    if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
1870                            (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
1871                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
1872                        snapToPageWithVelocity(finalPage, velocityX);
1873                    } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
1874                            (isFling && isVelocityXLeft)) &&
1875                            mCurrentPage < getChildCount() - 1) {
1876                        finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
1877                        snapToPageWithVelocity(finalPage, velocityX);
1878                    } else {
1879                        snapToDestination();
1880                    }
1881                } else {
1882                    if (!mScroller.isFinished()) {
1883                        abortScrollerAnimation(true);
1884                    }
1885
1886                    float scaleX = getScaleX();
1887                    int vX = (int) (-velocityX * scaleX);
1888                    int initialScrollX = (int) (getScrollX() * scaleX);
1889
1890                    mScroller.fling(initialScrollX,
1891                            getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
1892                    invalidate();
1893                }
1894            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1895                // at this point we have not moved beyond the touch slop
1896                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1897                // we can just page
1898                int nextPage = Math.max(0, mCurrentPage - 1);
1899                if (nextPage != mCurrentPage) {
1900                    snapToPage(nextPage);
1901                } else {
1902                    snapToDestination();
1903                }
1904            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1905                // at this point we have not moved beyond the touch slop
1906                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1907                // we can just page
1908                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1909                if (nextPage != mCurrentPage) {
1910                    snapToPage(nextPage);
1911                } else {
1912                    snapToDestination();
1913                }
1914            } else if (mTouchState == TOUCH_STATE_REORDERING) {
1915                // Update the last motion position
1916                mLastMotionX = ev.getX();
1917                mLastMotionY = ev.getY();
1918
1919                // Update the parent down so that our zoom animations take this new movement into
1920                // account
1921                float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1922                mParentDownMotionX = pt[0];
1923                mParentDownMotionY = pt[1];
1924                updateDragViewTranslationDuringDrag();
1925                boolean handledFling = false;
1926                if (!DISABLE_FLING_TO_DELETE) {
1927                    // Check the velocity and see if we are flinging-to-delete
1928                    PointF flingToDeleteVector = isFlingingToDelete();
1929                    if (flingToDeleteVector != null) {
1930                        onFlingToDelete(flingToDeleteVector);
1931                        handledFling = true;
1932                    }
1933                }
1934                if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
1935                        (int) mParentDownMotionY)) {
1936                    onDropToDelete();
1937                }
1938            } else {
1939                if (!mCancelTap) {
1940                    onUnhandledTap(ev);
1941                }
1942            }
1943
1944            // Remove the callback to wait for the side page hover timeout
1945            removeCallbacks(mSidePageHoverRunnable);
1946            // End any intermediate reordering states
1947            resetTouchState();
1948            break;
1949
1950        case MotionEvent.ACTION_CANCEL:
1951            if (mTouchState == TOUCH_STATE_SCROLLING) {
1952                snapToDestination();
1953            }
1954            resetTouchState();
1955            break;
1956
1957        case MotionEvent.ACTION_POINTER_UP:
1958            onSecondaryPointerUp(ev);
1959            releaseVelocityTracker();
1960            break;
1961        }
1962
1963        return true;
1964    }
1965
1966    public void onFlingToDelete(View v) {}
1967    public void onRemoveView(View v, boolean deletePermanently) {}
1968    public void onRemoveViewAnimationCompleted() {}
1969    public void onAddView(View v, int index) {}
1970
1971    private void resetTouchState() {
1972        releaseVelocityTracker();
1973        endReordering();
1974        mCancelTap = false;
1975        mScrollAbortedFromIntercept = false;
1976        mTouchState = TOUCH_STATE_REST;
1977        mActivePointerId = INVALID_POINTER;
1978    }
1979
1980    protected void onUnhandledTap(MotionEvent ev) {
1981        ((Launcher) getContext()).onClick(this);
1982    }
1983
1984    @Override
1985    public boolean onGenericMotionEvent(MotionEvent event) {
1986        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1987            switch (event.getAction()) {
1988                case MotionEvent.ACTION_SCROLL: {
1989                    // Handle mouse (or ext. device) by shifting the page depending on the scroll
1990                    final float vscroll;
1991                    final float hscroll;
1992                    if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1993                        vscroll = 0;
1994                        hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1995                    } else {
1996                        vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1997                        hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1998                    }
1999                    if (hscroll != 0 || vscroll != 0) {
2000                        boolean isForwardScroll = isLayoutRtl() ? (hscroll < 0 || vscroll < 0)
2001                                                         : (hscroll > 0 || vscroll > 0);
2002                        if (isForwardScroll) {
2003                            scrollRight();
2004                        } else {
2005                            scrollLeft();
2006                        }
2007                        return true;
2008                    }
2009                }
2010            }
2011        }
2012        return super.onGenericMotionEvent(event);
2013    }
2014
2015    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
2016        if (mVelocityTracker == null) {
2017            mVelocityTracker = VelocityTracker.obtain();
2018        }
2019        mVelocityTracker.addMovement(ev);
2020    }
2021
2022    private void releaseVelocityTracker() {
2023        if (mVelocityTracker != null) {
2024            mVelocityTracker.clear();
2025            mVelocityTracker.recycle();
2026            mVelocityTracker = null;
2027        }
2028    }
2029
2030    private void onSecondaryPointerUp(MotionEvent ev) {
2031        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
2032                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
2033        final int pointerId = ev.getPointerId(pointerIndex);
2034        if (pointerId == mActivePointerId) {
2035            // This was our active pointer going up. Choose a new
2036            // active pointer and adjust accordingly.
2037            // TODO: Make this decision more intelligent.
2038            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2039            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
2040            mLastMotionY = ev.getY(newPointerIndex);
2041            mLastMotionXRemainder = 0;
2042            mActivePointerId = ev.getPointerId(newPointerIndex);
2043            if (mVelocityTracker != null) {
2044                mVelocityTracker.clear();
2045            }
2046        }
2047    }
2048
2049    @Override
2050    public void requestChildFocus(View child, View focused) {
2051        super.requestChildFocus(child, focused);
2052        int page = indexToPage(indexOfChild(child));
2053        if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
2054            snapToPage(page);
2055        }
2056    }
2057
2058    protected int getChildWidth(int index) {
2059        return getPageAt(index).getMeasuredWidth();
2060    }
2061
2062    int getPageNearestToPoint(float x) {
2063        int index = 0;
2064        for (int i = 0; i < getChildCount(); ++i) {
2065            if (x < getChildAt(i).getRight() - getScrollX()) {
2066                return index;
2067            } else {
2068                index++;
2069            }
2070        }
2071        return Math.min(index, getChildCount() - 1);
2072    }
2073
2074    int getPageNearestToCenterOfScreen() {
2075        int minDistanceFromScreenCenter = Integer.MAX_VALUE;
2076        int minDistanceFromScreenCenterIndex = -1;
2077        int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
2078        final int childCount = getChildCount();
2079        for (int i = 0; i < childCount; ++i) {
2080            View layout = (View) getPageAt(i);
2081            int childWidth = layout.getMeasuredWidth();
2082            int halfChildWidth = (childWidth / 2);
2083            int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
2084            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
2085            if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
2086                minDistanceFromScreenCenter = distanceFromScreenCenter;
2087                minDistanceFromScreenCenterIndex = i;
2088            }
2089        }
2090        return minDistanceFromScreenCenterIndex;
2091    }
2092
2093    protected void snapToDestination() {
2094        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
2095    }
2096
2097    private static class ScrollInterpolator implements Interpolator {
2098        public ScrollInterpolator() {
2099        }
2100
2101        public float getInterpolation(float t) {
2102            t -= 1.0f;
2103            return t*t*t*t*t + 1;
2104        }
2105    }
2106
2107    // We want the duration of the page snap animation to be influenced by the distance that
2108    // the screen has to travel, however, we don't want this duration to be effected in a
2109    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
2110    // of travel has on the overall snap duration.
2111    float distanceInfluenceForSnapDuration(float f) {
2112        f -= 0.5f; // center the values about 0.
2113        f *= 0.3f * Math.PI / 2.0f;
2114        return (float) Math.sin(f);
2115    }
2116
2117    protected void snapToPageWithVelocity(int whichPage, int velocity) {
2118        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
2119        int halfScreenSize = getViewportWidth() / 2;
2120
2121        final int newX = getScrollForPage(whichPage);
2122        int delta = newX - mUnboundedScrollX;
2123        int duration = 0;
2124
2125        if (Math.abs(velocity) < mMinFlingVelocity) {
2126            // If the velocity is low enough, then treat this more as an automatic page advance
2127            // as opposed to an apparent physical response to flinging
2128            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
2129            return;
2130        }
2131
2132        // Here we compute a "distance" that will be used in the computation of the overall
2133        // snap duration. This is a function of the actual distance that needs to be traveled;
2134        // we keep this value close to half screen size in order to reduce the variance in snap
2135        // duration as a function of the distance the page needs to travel.
2136        float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
2137        float distance = halfScreenSize + halfScreenSize *
2138                distanceInfluenceForSnapDuration(distanceRatio);
2139
2140        velocity = Math.abs(velocity);
2141        velocity = Math.max(mMinSnapVelocity, velocity);
2142
2143        // we want the page's snap velocity to approximately match the velocity at which the
2144        // user flings, so we scale the duration by a value near to the derivative of the scroll
2145        // interpolator at zero, ie. 5. We use 4 to make it a little slower.
2146        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
2147
2148        snapToPage(whichPage, delta, duration);
2149    }
2150
2151    protected void snapToPage(int whichPage) {
2152        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
2153    }
2154
2155    protected void snapToPageImmediately(int whichPage) {
2156        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true);
2157    }
2158
2159    protected void snapToPage(int whichPage, int duration) {
2160        snapToPage(whichPage, duration, false);
2161    }
2162
2163    protected void snapToPage(int whichPage, int duration, boolean immediate) {
2164        whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
2165
2166        int newX = getScrollForPage(whichPage);
2167        final int sX = mUnboundedScrollX;
2168        final int delta = newX - sX;
2169        snapToPage(whichPage, delta, duration, immediate);
2170    }
2171
2172    protected void snapToPage(int whichPage, int delta, int duration) {
2173        snapToPage(whichPage, delta, duration, false);
2174    }
2175
2176    protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
2177        mNextPage = whichPage;
2178        View focusedChild = getFocusedChild();
2179        if (focusedChild != null && whichPage != mCurrentPage &&
2180                focusedChild == getPageAt(mCurrentPage)) {
2181            focusedChild.clearFocus();
2182        }
2183
2184        sendScrollAccessibilityEvent();
2185
2186        pageBeginMoving();
2187        awakenScrollBars(duration);
2188        if (immediate) {
2189            duration = 0;
2190        } else if (duration == 0) {
2191            duration = Math.abs(delta);
2192        }
2193
2194        if (!mScroller.isFinished()) {
2195            mScroller.abortAnimation();
2196        }
2197        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
2198
2199        notifyPageSwitchListener();
2200
2201        // Trigger a compute() to finish switching pages if necessary
2202        if (immediate) {
2203            computeScroll();
2204        }
2205
2206        // Defer loading associated pages until the scroll settles
2207        mDeferLoadAssociatedPagesUntilScrollCompletes = true;
2208
2209        mForceScreenScrolled = true;
2210        invalidate();
2211    }
2212
2213    public void scrollLeft() {
2214        if (getNextPage() > 0) snapToPage(getNextPage() - 1);
2215    }
2216
2217    public void scrollRight() {
2218        if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
2219    }
2220
2221    public int getPageForView(View v) {
2222        int result = -1;
2223        if (v != null) {
2224            ViewParent vp = v.getParent();
2225            int count = getChildCount();
2226            for (int i = 0; i < count; i++) {
2227                if (vp == getPageAt(i)) {
2228                    return i;
2229                }
2230            }
2231        }
2232        return result;
2233    }
2234
2235    /**
2236     * @return True is long presses are still allowed for the current touch
2237     */
2238    public boolean allowLongPress() {
2239        return mAllowLongPress;
2240    }
2241
2242    @Override
2243    public boolean performLongClick() {
2244        mCancelTap = true;
2245        return super.performLongClick();
2246    }
2247
2248    /**
2249     * Set true to allow long-press events to be triggered, usually checked by
2250     * {@link Launcher} to accept or block dpad-initiated long-presses.
2251     */
2252    public void setAllowLongPress(boolean allowLongPress) {
2253        mAllowLongPress = allowLongPress;
2254    }
2255
2256    public static class SavedState extends BaseSavedState {
2257        int currentPage = -1;
2258
2259        SavedState(Parcelable superState) {
2260            super(superState);
2261        }
2262
2263        private SavedState(Parcel in) {
2264            super(in);
2265            currentPage = in.readInt();
2266        }
2267
2268        @Override
2269        public void writeToParcel(Parcel out, int flags) {
2270            super.writeToParcel(out, flags);
2271            out.writeInt(currentPage);
2272        }
2273
2274        public static final Parcelable.Creator<SavedState> CREATOR =
2275                new Parcelable.Creator<SavedState>() {
2276            public SavedState createFromParcel(Parcel in) {
2277                return new SavedState(in);
2278            }
2279
2280            public SavedState[] newArray(int size) {
2281                return new SavedState[size];
2282            }
2283        };
2284    }
2285
2286    protected void loadAssociatedPages(int page) {
2287        loadAssociatedPages(page, false);
2288    }
2289    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
2290        if (mContentIsRefreshable) {
2291            final int count = getChildCount();
2292            if (page < count) {
2293                int lowerPageBound = getAssociatedLowerPageBound(page);
2294                int upperPageBound = getAssociatedUpperPageBound(page);
2295                if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
2296                        + upperPageBound);
2297                // First, clear any pages that should no longer be loaded
2298                for (int i = 0; i < count; ++i) {
2299                    Page layout = (Page) getPageAt(i);
2300                    if ((i < lowerPageBound) || (i > upperPageBound)) {
2301                        if (layout.getPageChildCount() > 0) {
2302                            layout.removeAllViewsOnPage();
2303                        }
2304                        mDirtyPageContent.set(i, true);
2305                    }
2306                }
2307                // Next, load any new pages
2308                for (int i = 0; i < count; ++i) {
2309                    if ((i != page) && immediateAndOnly) {
2310                        continue;
2311                    }
2312                    if (lowerPageBound <= i && i <= upperPageBound) {
2313                        if (mDirtyPageContent.get(i)) {
2314                            syncPageItems(i, (i == page) && immediateAndOnly);
2315                            mDirtyPageContent.set(i, false);
2316                        }
2317                    }
2318                }
2319            }
2320        }
2321    }
2322
2323    protected int getAssociatedLowerPageBound(int page) {
2324        return Math.max(0, page - 1);
2325    }
2326    protected int getAssociatedUpperPageBound(int page) {
2327        final int count = getChildCount();
2328        return Math.min(page + 1, count - 1);
2329    }
2330
2331    /**
2332     * This method is called ONLY to synchronize the number of pages that the paged view has.
2333     * To actually fill the pages with information, implement syncPageItems() below.  It is
2334     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
2335     * and therefore, individual page items do not need to be updated in this method.
2336     */
2337    public abstract void syncPages();
2338
2339    /**
2340     * This method is called to synchronize the items that are on a particular page.  If views on
2341     * the page can be reused, then they should be updated within this method.
2342     */
2343    public abstract void syncPageItems(int page, boolean immediate);
2344
2345    protected void invalidatePageData() {
2346        invalidatePageData(-1, false);
2347    }
2348    protected void invalidatePageData(int currentPage) {
2349        invalidatePageData(currentPage, false);
2350    }
2351    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
2352        if (!mIsDataReady) {
2353            return;
2354        }
2355
2356        if (mContentIsRefreshable) {
2357            // Force all scrolling-related behavior to end
2358            forceFinishScroller();
2359
2360            // Update all the pages
2361            syncPages();
2362
2363            // We must force a measure after we've loaded the pages to update the content width and
2364            // to determine the full scroll width
2365            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
2366                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
2367
2368            // Set a new page as the current page if necessary
2369            if (currentPage > -1) {
2370                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
2371            }
2372
2373            // Mark each of the pages as dirty
2374            final int count = getChildCount();
2375            mDirtyPageContent.clear();
2376            for (int i = 0; i < count; ++i) {
2377                mDirtyPageContent.add(true);
2378            }
2379
2380            // Load any pages that are necessary for the current window of views
2381            loadAssociatedPages(mCurrentPage, immediateAndOnly);
2382            requestLayout();
2383        }
2384        if (isPageMoving()) {
2385            // If the page is moving, then snap it to the final position to ensure we don't get
2386            // stuck between pages
2387            snapToDestination();
2388        }
2389    }
2390
2391    // Animate the drag view back to the original position
2392    void animateDragViewToOriginalPosition() {
2393        if (mDragView != null) {
2394            AnimatorSet anim = new AnimatorSet();
2395            anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
2396            anim.playTogether(
2397                    ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
2398                    ObjectAnimator.ofFloat(mDragView, "translationY", 0f),
2399                    ObjectAnimator.ofFloat(mDragView, "scaleX", 1f),
2400                    ObjectAnimator.ofFloat(mDragView, "scaleY", 1f));
2401            anim.addListener(new AnimatorListenerAdapter() {
2402                @Override
2403                public void onAnimationEnd(Animator animation) {
2404                    onPostReorderingAnimationCompleted();
2405                }
2406            });
2407            anim.start();
2408        }
2409    }
2410
2411    protected void onStartReordering() {
2412        // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
2413        mTouchState = TOUCH_STATE_REORDERING;
2414        mIsReordering = true;
2415
2416        // We must invalidate to trigger a redraw to update the layers such that the drag view
2417        // is always drawn on top
2418        invalidate();
2419    }
2420
2421    private void onPostReorderingAnimationCompleted() {
2422        // Trigger the callback when reordering has settled
2423        --mPostReorderingPreZoomInRemainingAnimationCount;
2424        if (mPostReorderingPreZoomInRunnable != null &&
2425                mPostReorderingPreZoomInRemainingAnimationCount == 0) {
2426            mPostReorderingPreZoomInRunnable.run();
2427            mPostReorderingPreZoomInRunnable = null;
2428        }
2429    }
2430
2431    protected void onEndReordering() {
2432        mIsReordering = false;
2433    }
2434
2435    public boolean startReordering(View v) {
2436        int dragViewIndex = indexOfChild(v);
2437
2438        if (mTouchState != TOUCH_STATE_REST) return false;
2439
2440        mTempVisiblePagesRange[0] = 0;
2441        mTempVisiblePagesRange[1] = getPageCount() - 1;
2442        getOverviewModePages(mTempVisiblePagesRange);
2443        mReorderingStarted = true;
2444
2445        // Check if we are within the reordering range
2446        if (mTempVisiblePagesRange[0] <= dragViewIndex &&
2447            dragViewIndex <= mTempVisiblePagesRange[1]) {
2448            // Find the drag view under the pointer
2449            mDragView = getChildAt(dragViewIndex);
2450            mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
2451            mDragViewBaselineLeft = mDragView.getLeft();
2452            disableFreeScroll(-1);
2453            onStartReordering();
2454            return true;
2455        }
2456        return false;
2457    }
2458
2459    boolean isReordering(boolean testTouchState) {
2460        boolean state = mIsReordering;
2461        if (testTouchState) {
2462            state &= (mTouchState == TOUCH_STATE_REORDERING);
2463        }
2464        return state;
2465    }
2466    void endReordering() {
2467        // For simplicity, we call endReordering sometimes even if reordering was never started.
2468        // In that case, we don't want to do anything.
2469        if (!mReorderingStarted) return;
2470        mReorderingStarted = false;
2471
2472        // If we haven't flung-to-delete the current child, then we just animate the drag view
2473        // back into position
2474        final Runnable onCompleteRunnable = new Runnable() {
2475            @Override
2476            public void run() {
2477                onEndReordering();
2478            }
2479        };
2480        if (!mDeferringForDelete) {
2481            mPostReorderingPreZoomInRunnable = new Runnable() {
2482                public void run() {
2483                    onCompleteRunnable.run();
2484                    enableFreeScroll();
2485                };
2486            };
2487
2488            mPostReorderingPreZoomInRemainingAnimationCount =
2489                    NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
2490            // Snap to the current page
2491            snapToPage(indexOfChild(mDragView), 0);
2492            // Animate the drag view back to the front position
2493            animateDragViewToOriginalPosition();
2494        } else {
2495            // Handled in post-delete-animation-callbacks
2496        }
2497    }
2498
2499    /*
2500     * Flinging to delete - IN PROGRESS
2501     */
2502    private PointF isFlingingToDelete() {
2503        ViewConfiguration config = ViewConfiguration.get(getContext());
2504        mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
2505
2506        if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
2507            // Do a quick dot product test to ensure that we are flinging upwards
2508            PointF vel = new PointF(mVelocityTracker.getXVelocity(),
2509                    mVelocityTracker.getYVelocity());
2510            PointF upVec = new PointF(0f, -1f);
2511            float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
2512                    (vel.length() * upVec.length()));
2513            if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
2514                return vel;
2515            }
2516        }
2517        return null;
2518    }
2519
2520    /**
2521     * Creates an animation from the current drag view along its current velocity vector.
2522     * For this animation, the alpha runs for a fixed duration and we update the position
2523     * progressively.
2524     */
2525    private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
2526        private View mDragView;
2527        private PointF mVelocity;
2528        private Rect mFrom;
2529        private long mPrevTime;
2530        private float mFriction;
2531
2532        private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
2533
2534        public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
2535                long startTime, float friction) {
2536            mDragView = dragView;
2537            mVelocity = vel;
2538            mFrom = from;
2539            mPrevTime = startTime;
2540            mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
2541        }
2542
2543        @Override
2544        public void onAnimationUpdate(ValueAnimator animation) {
2545            float t = ((Float) animation.getAnimatedValue()).floatValue();
2546            long curTime = AnimationUtils.currentAnimationTimeMillis();
2547
2548            mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
2549            mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
2550
2551            mDragView.setTranslationX(mFrom.left);
2552            mDragView.setTranslationY(mFrom.top);
2553            mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
2554
2555            mVelocity.x *= mFriction;
2556            mVelocity.y *= mFriction;
2557            mPrevTime = curTime;
2558        }
2559    };
2560
2561    private static final int ANIM_TAG_KEY = 100;
2562
2563    private Runnable createPostDeleteAnimationRunnable(final View dragView) {
2564        return new Runnable() {
2565            @Override
2566            public void run() {
2567                int dragViewIndex = indexOfChild(dragView);
2568
2569                // For each of the pages around the drag view, animate them from the previous
2570                // position to the new position in the layout (as a result of the drag view moving
2571                // in the layout)
2572                // NOTE: We can make an assumption here because we have side-bound pages that we
2573                //       will always have pages to animate in from the left
2574                getOverviewModePages(mTempVisiblePagesRange);
2575                boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
2576                boolean slideFromLeft = (isLastWidgetPage ||
2577                        dragViewIndex > mTempVisiblePagesRange[0]);
2578
2579                // Setup the scroll to the correct page before we swap the views
2580                if (slideFromLeft) {
2581                    snapToPageImmediately(dragViewIndex - 1);
2582                }
2583
2584                int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
2585                int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
2586                int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
2587                int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
2588                ArrayList<Animator> animations = new ArrayList<Animator>();
2589                for (int i = lowerIndex; i <= upperIndex; ++i) {
2590                    View v = getChildAt(i);
2591                    // dragViewIndex < pageUnderPointIndex, so after we remove the
2592                    // drag view all subsequent views to pageUnderPointIndex will
2593                    // shift down.
2594                    int oldX = 0;
2595                    int newX = 0;
2596                    if (slideFromLeft) {
2597                        if (i == 0) {
2598                            // Simulate the page being offscreen with the page spacing
2599                            oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
2600                                    - mPageSpacing;
2601                        } else {
2602                            oldX = getViewportOffsetX() + getChildOffset(i - 1);
2603                        }
2604                        newX = getViewportOffsetX() + getChildOffset(i);
2605                    } else {
2606                        oldX = getChildOffset(i) - getChildOffset(i - 1);
2607                        newX = 0;
2608                    }
2609
2610                    // Animate the view translation from its old position to its new
2611                    // position
2612                    AnimatorSet anim = (AnimatorSet) v.getTag();
2613                    if (anim != null) {
2614                        anim.cancel();
2615                    }
2616
2617                    // Note: Hacky, but we want to skip any optimizations to not draw completely
2618                    // hidden views
2619                    v.setAlpha(Math.max(v.getAlpha(), 0.01f));
2620                    v.setTranslationX(oldX - newX);
2621                    anim = new AnimatorSet();
2622                    anim.playTogether(
2623                            ObjectAnimator.ofFloat(v, "translationX", 0f),
2624                            ObjectAnimator.ofFloat(v, "alpha", 1f));
2625                    animations.add(anim);
2626                    v.setTag(ANIM_TAG_KEY, anim);
2627                }
2628
2629                AnimatorSet slideAnimations = new AnimatorSet();
2630                slideAnimations.playTogether(animations);
2631                slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
2632                slideAnimations.addListener(new AnimatorListenerAdapter() {
2633                    @Override
2634                    public void onAnimationEnd(Animator animation) {
2635                        mDeferringForDelete = false;
2636                        onEndReordering();
2637                        onRemoveViewAnimationCompleted();
2638                    }
2639                });
2640                slideAnimations.start();
2641
2642                removeView(dragView);
2643                onRemoveView(dragView, true);
2644            }
2645        };
2646    }
2647
2648    public void onFlingToDelete(PointF vel) {
2649        final long startTime = AnimationUtils.currentAnimationTimeMillis();
2650
2651        // NOTE: Because it takes time for the first frame of animation to actually be
2652        // called and we expect the animation to be a continuation of the fling, we have
2653        // to account for the time that has elapsed since the fling finished.  And since
2654        // we don't have a startDelay, we will always get call to update when we call
2655        // start() (which we want to ignore).
2656        final TimeInterpolator tInterpolator = new TimeInterpolator() {
2657            private int mCount = -1;
2658            private long mStartTime;
2659            private float mOffset;
2660            /* Anonymous inner class ctor */ {
2661                mStartTime = startTime;
2662            }
2663
2664            @Override
2665            public float getInterpolation(float t) {
2666                if (mCount < 0) {
2667                    mCount++;
2668                } else if (mCount == 0) {
2669                    mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
2670                            mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
2671                    mCount++;
2672                }
2673                return Math.min(1f, mOffset + t);
2674            }
2675        };
2676
2677        final Rect from = new Rect();
2678        final View dragView = mDragView;
2679        from.left = (int) dragView.getTranslationX();
2680        from.top = (int) dragView.getTranslationY();
2681        AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
2682                from, startTime, FLING_TO_DELETE_FRICTION);
2683
2684        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2685
2686        // Create and start the animation
2687        ValueAnimator mDropAnim = new ValueAnimator();
2688        mDropAnim.setInterpolator(tInterpolator);
2689        mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
2690        mDropAnim.setFloatValues(0f, 1f);
2691        mDropAnim.addUpdateListener(updateCb);
2692        mDropAnim.addListener(new AnimatorListenerAdapter() {
2693            public void onAnimationEnd(Animator animation) {
2694                onAnimationEndRunnable.run();
2695            }
2696        });
2697        mDropAnim.start();
2698        mDeferringForDelete = true;
2699    }
2700
2701    /* Drag to delete */
2702    private boolean isHoveringOverDeleteDropTarget(int x, int y) {
2703        if (mDeleteDropTarget != null) {
2704            mAltTmpRect.set(0, 0, 0, 0);
2705            View parent = (View) mDeleteDropTarget.getParent();
2706            if (parent != null) {
2707                parent.getGlobalVisibleRect(mAltTmpRect);
2708            }
2709            mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
2710            mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
2711            return mTmpRect.contains(x, y);
2712        }
2713        return false;
2714    }
2715
2716    protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
2717
2718    private void onDropToDelete() {
2719        final View dragView = mDragView;
2720
2721        final float toScale = 0f;
2722        final float toAlpha = 0f;
2723
2724        // Create and start the complex animation
2725        ArrayList<Animator> animations = new ArrayList<Animator>();
2726        AnimatorSet motionAnim = new AnimatorSet();
2727        motionAnim.setInterpolator(new DecelerateInterpolator(2));
2728        motionAnim.playTogether(
2729                ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
2730                ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
2731        animations.add(motionAnim);
2732
2733        AnimatorSet alphaAnim = new AnimatorSet();
2734        alphaAnim.setInterpolator(new LinearInterpolator());
2735        alphaAnim.playTogether(
2736                ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
2737        animations.add(alphaAnim);
2738
2739        final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2740
2741        AnimatorSet anim = new AnimatorSet();
2742        anim.playTogether(animations);
2743        anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
2744        anim.addListener(new AnimatorListenerAdapter() {
2745            public void onAnimationEnd(Animator animation) {
2746                onAnimationEndRunnable.run();
2747            }
2748        });
2749        anim.start();
2750
2751        mDeferringForDelete = true;
2752    }
2753
2754    /* Accessibility */
2755    @Override
2756    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2757        super.onInitializeAccessibilityNodeInfo(info);
2758        info.setScrollable(getPageCount() > 1);
2759        if (getCurrentPage() < getPageCount() - 1) {
2760            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2761        }
2762        if (getCurrentPage() > 0) {
2763            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2764        }
2765    }
2766
2767    @Override
2768    public void sendAccessibilityEvent(int eventType) {
2769        // Don't let the view send real scroll events.
2770        if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2771            super.sendAccessibilityEvent(eventType);
2772        }
2773    }
2774
2775    @Override
2776    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2777        super.onInitializeAccessibilityEvent(event);
2778        event.setScrollable(true);
2779    }
2780
2781    @Override
2782    public boolean performAccessibilityAction(int action, Bundle arguments) {
2783        if (super.performAccessibilityAction(action, arguments)) {
2784            return true;
2785        }
2786        switch (action) {
2787            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2788                if (getCurrentPage() < getPageCount() - 1) {
2789                    scrollRight();
2790                    return true;
2791                }
2792            } break;
2793            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2794                if (getCurrentPage() > 0) {
2795                    scrollLeft();
2796                    return true;
2797                }
2798            } break;
2799        }
2800        return false;
2801    }
2802
2803    protected String getCurrentPageDescription() {
2804        return String.format(getContext().getString(R.string.default_scroll_format),
2805                getNextPage() + 1, getChildCount());
2806    }
2807
2808    @Override
2809    public boolean onHoverEvent(android.view.MotionEvent event) {
2810        return true;
2811    }
2812}
2813