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