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