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