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