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