1/*
2 * Copyright (C) 2010 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.launcher2;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.os.Bundle;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.view.InputDevice;
32import android.view.KeyEvent;
33import android.view.MotionEvent;
34import android.view.VelocityTracker;
35import android.view.View;
36import android.view.ViewConfiguration;
37import android.view.ViewGroup;
38import android.view.ViewParent;
39import android.view.accessibility.AccessibilityEvent;
40import android.view.accessibility.AccessibilityManager;
41import android.view.accessibility.AccessibilityNodeInfo;
42import android.view.animation.Interpolator;
43import android.widget.Scroller;
44
45import com.android.launcher.R;
46
47import java.util.ArrayList;
48
49/**
50 * An abstraction of the original Workspace which supports browsing through a
51 * sequential list of "pages"
52 */
53public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
54    private static final String TAG = "PagedView";
55    private static final boolean DEBUG = false;
56    protected static final int INVALID_PAGE = -1;
57
58    // the min drag distance for a fling to register, to prevent random page shifts
59    private static final int MIN_LENGTH_FOR_FLING = 25;
60
61    protected static final int PAGE_SNAP_ANIMATION_DURATION = 550;
62    protected static final int MAX_PAGE_SNAP_DURATION = 750;
63    protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
64    protected static final float NANOTIME_DIV = 1000000000.0f;
65
66    private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
67    private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
68
69    private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
70    // The page is moved more than halfway, automatically move to the next page on touch up.
71    private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
72
73    // The following constants need to be scaled based on density. The scaled versions will be
74    // assigned to the corresponding member variables below.
75    private static final int FLING_THRESHOLD_VELOCITY = 500;
76    private static final int MIN_SNAP_VELOCITY = 1500;
77    private static final int MIN_FLING_VELOCITY = 250;
78
79    static final int AUTOMATIC_PAGE_SPACING = -1;
80
81    protected int mFlingThresholdVelocity;
82    protected int mMinFlingVelocity;
83    protected int mMinSnapVelocity;
84
85    protected float mDensity;
86    protected float mSmoothingTime;
87    protected float mTouchX;
88
89    protected boolean mFirstLayout = true;
90
91    protected int mCurrentPage;
92    protected int mNextPage = INVALID_PAGE;
93    protected int mMaxScrollX;
94    protected Scroller mScroller;
95    private VelocityTracker mVelocityTracker;
96
97    private float mDownMotionX;
98    protected float mLastMotionX;
99    protected float mLastMotionXRemainder;
100    protected float mLastMotionY;
101    protected float mTotalMotionX;
102    private int mLastScreenCenter = -1;
103    private int[] mChildOffsets;
104    private int[] mChildRelativeOffsets;
105    private int[] mChildOffsetsWithLayoutScale;
106
107    protected final static int TOUCH_STATE_REST = 0;
108    protected final static int TOUCH_STATE_SCROLLING = 1;
109    protected final static int TOUCH_STATE_PREV_PAGE = 2;
110    protected final static int TOUCH_STATE_NEXT_PAGE = 3;
111    protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
112
113    protected int mTouchState = TOUCH_STATE_REST;
114    protected boolean mForceScreenScrolled = false;
115
116    protected OnLongClickListener mLongClickListener;
117
118    protected boolean mAllowLongPress = true;
119
120    protected int mTouchSlop;
121    private int mPagingTouchSlop;
122    private int mMaximumVelocity;
123    private int mMinimumWidth;
124    protected int mPageSpacing;
125    protected int mPageLayoutPaddingTop;
126    protected int mPageLayoutPaddingBottom;
127    protected int mPageLayoutPaddingLeft;
128    protected int mPageLayoutPaddingRight;
129    protected int mPageLayoutWidthGap;
130    protected int mPageLayoutHeightGap;
131    protected int mCellCountX = 0;
132    protected int mCellCountY = 0;
133    protected boolean mCenterPagesVertically;
134    protected boolean mAllowOverScroll = true;
135    protected int mUnboundedScrollX;
136    protected int[] mTempVisiblePagesRange = new int[2];
137    protected boolean mForceDrawAllChildrenNextFrame;
138
139    // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
140    // it is equal to the scaled overscroll position. We use a separate value so as to prevent
141    // the screens from continuing to translate beyond the normal bounds.
142    protected int mOverScrollX;
143
144    // parameter that adjusts the layout to be optimized for pages with that scale factor
145    protected float mLayoutScale = 1.0f;
146
147    protected static final int INVALID_POINTER = -1;
148
149    protected int mActivePointerId = INVALID_POINTER;
150
151    private PageSwitchListener mPageSwitchListener;
152
153    protected ArrayList<Boolean> mDirtyPageContent;
154
155    // If true, syncPages and syncPageItems will be called to refresh pages
156    protected boolean mContentIsRefreshable = true;
157
158    // If true, modify alpha of neighboring pages as user scrolls left/right
159    protected boolean mFadeInAdjacentScreens = true;
160
161    // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
162    // to switch to a new page
163    protected boolean mUsePagingTouchSlop = true;
164
165    // If true, the subclass should directly update scrollX itself in its computeScroll method
166    // (SmoothPagedView does this)
167    protected boolean mDeferScrollUpdate = false;
168
169    protected boolean mIsPageMoving = false;
170
171    // All syncs and layout passes are deferred until data is ready.
172    protected boolean mIsDataReady = false;
173
174    // Scrolling indicator
175    private ValueAnimator mScrollIndicatorAnimator;
176    private View mScrollIndicator;
177    private int mScrollIndicatorPaddingLeft;
178    private int mScrollIndicatorPaddingRight;
179    private boolean mHasScrollIndicator = true;
180    private boolean mShouldShowScrollIndicator = false;
181    private boolean mShouldShowScrollIndicatorImmediately = false;
182    protected static final int sScrollIndicatorFadeInDuration = 150;
183    protected static final int sScrollIndicatorFadeOutDuration = 650;
184    protected static final int sScrollIndicatorFlashDuration = 650;
185    private boolean mScrollingPaused = false;
186
187    // If set, will defer loading associated pages until the scrolling settles
188    private boolean mDeferLoadAssociatedPagesUntilScrollCompletes;
189
190    public interface PageSwitchListener {
191        void onPageSwitch(View newPage, int newPageIndex);
192    }
193
194    public PagedView(Context context) {
195        this(context, null);
196    }
197
198    public PagedView(Context context, AttributeSet attrs) {
199        this(context, attrs, 0);
200    }
201
202    public PagedView(Context context, AttributeSet attrs, int defStyle) {
203        super(context, attrs, defStyle);
204
205        TypedArray a = context.obtainStyledAttributes(attrs,
206                R.styleable.PagedView, defStyle, 0);
207        setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
208        mPageLayoutPaddingTop = a.getDimensionPixelSize(
209                R.styleable.PagedView_pageLayoutPaddingTop, 0);
210        mPageLayoutPaddingBottom = a.getDimensionPixelSize(
211                R.styleable.PagedView_pageLayoutPaddingBottom, 0);
212        mPageLayoutPaddingLeft = a.getDimensionPixelSize(
213                R.styleable.PagedView_pageLayoutPaddingLeft, 0);
214        mPageLayoutPaddingRight = a.getDimensionPixelSize(
215                R.styleable.PagedView_pageLayoutPaddingRight, 0);
216        mPageLayoutWidthGap = a.getDimensionPixelSize(
217                R.styleable.PagedView_pageLayoutWidthGap, 0);
218        mPageLayoutHeightGap = a.getDimensionPixelSize(
219                R.styleable.PagedView_pageLayoutHeightGap, 0);
220        mScrollIndicatorPaddingLeft =
221            a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
222        mScrollIndicatorPaddingRight =
223            a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
224        a.recycle();
225
226        setHapticFeedbackEnabled(false);
227        init();
228    }
229
230    /**
231     * Initializes various states for this workspace.
232     */
233    protected void init() {
234        mDirtyPageContent = new ArrayList<Boolean>();
235        mDirtyPageContent.ensureCapacity(32);
236        mScroller = new Scroller(getContext(), new ScrollInterpolator());
237        mCurrentPage = 0;
238        mCenterPagesVertically = true;
239
240        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
241        mTouchSlop = configuration.getScaledTouchSlop();
242        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
243        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
244        mDensity = getResources().getDisplayMetrics().density;
245
246        mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
247        mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
248        mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
249        setOnHierarchyChangeListener(this);
250    }
251
252    public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
253        mPageSwitchListener = pageSwitchListener;
254        if (mPageSwitchListener != null) {
255            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
256        }
257    }
258
259    /**
260     * Note: this is a reimplementation of View.isLayoutRtl() since that is currently hidden api.
261     */
262    public boolean isLayoutRtl() {
263        return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
264    }
265
266    /**
267     * Called by subclasses to mark that data is ready, and that we can begin loading and laying
268     * out pages.
269     */
270    protected void setDataIsReady() {
271        mIsDataReady = true;
272    }
273    protected boolean isDataReady() {
274        return mIsDataReady;
275    }
276
277    /**
278     * Returns the index of the currently displayed page.
279     *
280     * @return The index of the currently displayed page.
281     */
282    int getCurrentPage() {
283        return mCurrentPage;
284    }
285    int getNextPage() {
286        return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
287    }
288
289    int getPageCount() {
290        return getChildCount();
291    }
292
293    View getPageAt(int index) {
294        return getChildAt(index);
295    }
296
297    protected int indexToPage(int index) {
298        return index;
299    }
300
301    /**
302     * Updates the scroll of the current page immediately to its final scroll position.  We use this
303     * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
304     * the previous tab page.
305     */
306    protected void updateCurrentPageScroll() {
307        // If the current page is invalid, just reset the scroll position to zero
308        int newX = 0;
309        if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
310            int offset = getChildOffset(mCurrentPage);
311            int relOffset = getRelativeChildOffset(mCurrentPage);
312            newX = offset - relOffset;
313        }
314        scrollTo(newX, 0);
315        mScroller.setFinalX(newX);
316        mScroller.forceFinished(true);
317    }
318
319    /**
320     * Called during AllApps/Home transitions to avoid unnecessary work. When that other animation
321     * ends, {@link #resumeScrolling()} should be called, along with
322     * {@link #updateCurrentPageScroll()} to correctly set the final state and re-enable scrolling.
323     */
324    void pauseScrolling() {
325        mScroller.forceFinished(true);
326        cancelScrollingIndicatorAnimations();
327        mScrollingPaused = true;
328    }
329
330    /**
331     * Enables scrolling again.
332     * @see #pauseScrolling()
333     */
334    void resumeScrolling() {
335        mScrollingPaused = false;
336    }
337    /**
338     * Sets the current page.
339     */
340    void setCurrentPage(int currentPage) {
341        if (!mScroller.isFinished()) {
342            mScroller.abortAnimation();
343        }
344        // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
345        // the default
346        if (getChildCount() == 0) {
347            return;
348        }
349
350
351        mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
352        updateCurrentPageScroll();
353        updateScrollingIndicator();
354        notifyPageSwitchListener();
355        invalidate();
356    }
357
358    protected void notifyPageSwitchListener() {
359        if (mPageSwitchListener != null) {
360            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
361        }
362    }
363
364    protected void pageBeginMoving() {
365        if (!mIsPageMoving) {
366            mIsPageMoving = true;
367            onPageBeginMoving();
368        }
369    }
370
371    protected void pageEndMoving() {
372        if (mIsPageMoving) {
373            mIsPageMoving = false;
374            onPageEndMoving();
375        }
376    }
377
378    protected boolean isPageMoving() {
379        return mIsPageMoving;
380    }
381
382    // a method that subclasses can override to add behavior
383    protected void onPageBeginMoving() {
384    }
385
386    // a method that subclasses can override to add behavior
387    protected void onPageEndMoving() {
388    }
389
390    /**
391     * Registers the specified listener on each page contained in this workspace.
392     *
393     * @param l The listener used to respond to long clicks.
394     */
395    @Override
396    public void setOnLongClickListener(OnLongClickListener l) {
397        mLongClickListener = l;
398        final int count = getPageCount();
399        for (int i = 0; i < count; i++) {
400            getPageAt(i).setOnLongClickListener(l);
401        }
402    }
403
404    @Override
405    public void scrollBy(int x, int y) {
406        scrollTo(mUnboundedScrollX + x, getScrollY() + y);
407    }
408
409    @Override
410    public void scrollTo(int x, int y) {
411        final boolean isRtl = isLayoutRtl();
412        mUnboundedScrollX = x;
413
414        boolean isXBeforeFirstPage = isRtl ? (x > mMaxScrollX) : (x < 0);
415        boolean isXAfterLastPage = isRtl ? (x < 0) : (x > mMaxScrollX);
416        if (isXBeforeFirstPage) {
417            super.scrollTo(0, y);
418            if (mAllowOverScroll) {
419                if (isRtl) {
420                    overScroll(x - mMaxScrollX);
421                } else {
422                    overScroll(x);
423                }
424            }
425        } else if (isXAfterLastPage) {
426            super.scrollTo(mMaxScrollX, y);
427            if (mAllowOverScroll) {
428                if (isRtl) {
429                    overScroll(x);
430                } else {
431                    overScroll(x - mMaxScrollX);
432                }
433            }
434        } else {
435            mOverScrollX = x;
436            super.scrollTo(x, y);
437        }
438
439        mTouchX = x;
440        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
441    }
442
443    // we moved this functionality to a helper function so SmoothPagedView can reuse it
444    protected boolean computeScrollHelper() {
445        if (mScroller.computeScrollOffset()) {
446            // Don't bother scrolling if the page does not need to be moved
447            if (getScrollX() != mScroller.getCurrX()
448                || getScrollY() != mScroller.getCurrY()
449                || mOverScrollX != mScroller.getCurrX()) {
450                scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
451            }
452            invalidate();
453            return true;
454        } else if (mNextPage != INVALID_PAGE) {
455            mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
456            mNextPage = INVALID_PAGE;
457            notifyPageSwitchListener();
458
459            // Load the associated pages if necessary
460            if (mDeferLoadAssociatedPagesUntilScrollCompletes) {
461                loadAssociatedPages(mCurrentPage);
462                mDeferLoadAssociatedPagesUntilScrollCompletes = false;
463            }
464
465            // We don't want to trigger a page end moving unless the page has settled
466            // and the user has stopped scrolling
467            if (mTouchState == TOUCH_STATE_REST) {
468                pageEndMoving();
469            }
470
471            // Notify the user when the page changes
472            AccessibilityManager accessibilityManager = (AccessibilityManager)
473                    getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
474            if (accessibilityManager.isEnabled()) {
475                AccessibilityEvent ev =
476                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
477                ev.getText().add(getCurrentPageDescription());
478                sendAccessibilityEventUnchecked(ev);
479            }
480            return true;
481        }
482        return false;
483    }
484
485    @Override
486    public void computeScroll() {
487        computeScrollHelper();
488    }
489
490    @Override
491    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
492        if (!mIsDataReady) {
493            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
494            return;
495        }
496
497        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
498        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
499        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
500        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
501        if (widthMode != MeasureSpec.EXACTLY) {
502            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
503        }
504
505        // Return early if we aren't given a proper dimension
506        if (widthSize <= 0 || heightSize <= 0) {
507            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
508            return;
509        }
510
511        /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
512         * of the All apps view on XLarge displays to not take up more space then it needs. Width
513         * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
514         * each page to have the same width.
515         */
516        int maxChildHeight = 0;
517
518        final int verticalPadding = getPaddingTop() + getPaddingBottom();
519        final int horizontalPadding = getPaddingLeft() + getPaddingRight();
520
521
522        // The children are given the same width and height as the workspace
523        // unless they were set to WRAP_CONTENT
524        if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
525        final int childCount = getChildCount();
526        for (int i = 0; i < childCount; i++) {
527            // disallowing padding in paged view (just pass 0)
528            final View child = getPageAt(i);
529            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
530
531            int childWidthMode;
532            if (lp.width == LayoutParams.WRAP_CONTENT) {
533                childWidthMode = MeasureSpec.AT_MOST;
534            } else {
535                childWidthMode = MeasureSpec.EXACTLY;
536            }
537
538            int childHeightMode;
539            if (lp.height == LayoutParams.WRAP_CONTENT) {
540                childHeightMode = MeasureSpec.AT_MOST;
541            } else {
542                childHeightMode = MeasureSpec.EXACTLY;
543            }
544
545            final int childWidthMeasureSpec =
546                MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
547            final int childHeightMeasureSpec =
548                MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
549
550            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
551            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
552            if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", "
553                    + child.getMeasuredHeight());
554        }
555
556        if (heightMode == MeasureSpec.AT_MOST) {
557            heightSize = maxChildHeight + verticalPadding;
558        }
559
560        setMeasuredDimension(widthSize, heightSize);
561
562        // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
563        // We also wait until we set the measured dimensions before flushing the cache as well, to
564        // ensure that the cache is filled with good values.
565        invalidateCachedOffsets();
566
567        if (childCount > 0) {
568            if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", "
569                    + getChildWidth(0));
570
571            // Calculate the variable page spacing if necessary
572            if (mPageSpacing == AUTOMATIC_PAGE_SPACING) {
573                // The gap between pages in the PagedView should be equal to the gap from the page
574                // to the edge of the screen (so it is not visible in the current screen).  To
575                // account for unequal padding on each side of the paged view, we take the maximum
576                // of the left/right gap and use that as the gap between each page.
577                int offset = getRelativeChildOffset(0);
578                int spacing = Math.max(offset, widthSize - offset -
579                        getChildAt(0).getMeasuredWidth());
580                setPageSpacing(spacing);
581            }
582        }
583
584        updateScrollingIndicatorPosition();
585
586        if (childCount > 0) {
587            final int index = isLayoutRtl() ? 0 : childCount - 1;
588            mMaxScrollX = getChildOffset(index) - getRelativeChildOffset(index);
589        } else {
590            mMaxScrollX = 0;
591        }
592    }
593
594    protected void scrollToNewPageWithoutMovingPages(int newCurrentPage) {
595        int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage);
596        int delta = newX - getScrollX();
597
598        final int pageCount = getChildCount();
599        for (int i = 0; i < pageCount; i++) {
600            View page = (View) getPageAt(i);
601            page.setX(page.getX() + delta);
602        }
603        setCurrentPage(newCurrentPage);
604    }
605
606    // A layout scale of 1.0f assumes that the pages, in their unshrunken state, have a
607    // scale of 1.0f. A layout scale of 0.8f assumes the pages have a scale of 0.8f, and
608    // tightens the layout accordingly
609    public void setLayoutScale(float childrenScale) {
610        mLayoutScale = childrenScale;
611        invalidateCachedOffsets();
612
613        // Now we need to do a re-layout, but preserving absolute X and Y coordinates
614        int childCount = getChildCount();
615        float childrenX[] = new float[childCount];
616        float childrenY[] = new float[childCount];
617        for (int i = 0; i < childCount; i++) {
618            final View child = getPageAt(i);
619            childrenX[i] = child.getX();
620            childrenY[i] = child.getY();
621        }
622        // Trigger a full re-layout (never just call onLayout directly!)
623        int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
624        int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY);
625        requestLayout();
626        measure(widthSpec, heightSpec);
627        layout(getLeft(), getTop(), getRight(), getBottom());
628        for (int i = 0; i < childCount; i++) {
629            final View child = getPageAt(i);
630            child.setX(childrenX[i]);
631            child.setY(childrenY[i]);
632        }
633
634        // Also, the page offset has changed  (since the pages are now smaller);
635        // update the page offset, but again preserving absolute X and Y coordinates
636        scrollToNewPageWithoutMovingPages(mCurrentPage);
637    }
638
639    public void setPageSpacing(int pageSpacing) {
640        mPageSpacing = pageSpacing;
641        invalidateCachedOffsets();
642    }
643
644    @Override
645    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
646        if (!mIsDataReady) {
647            return;
648        }
649
650        if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
651        final int verticalPadding = getPaddingTop() + getPaddingBottom();
652        final int childCount = getChildCount();
653        final boolean isRtl = isLayoutRtl();
654
655        final int startIndex = isRtl ? childCount - 1 : 0;
656        final int endIndex = isRtl ? -1 : childCount;
657        final int delta = isRtl ? -1 : 1;
658        int childLeft = getRelativeChildOffset(startIndex);
659        for (int i = startIndex; i != endIndex; i += delta) {
660            final View child = getPageAt(i);
661            if (child.getVisibility() != View.GONE) {
662                final int childWidth = getScaledMeasuredWidth(child);
663                final int childHeight = child.getMeasuredHeight();
664                int childTop = getPaddingTop();
665                if (mCenterPagesVertically) {
666                    childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
667                }
668
669                if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
670                child.layout(childLeft, childTop,
671                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
672                childLeft += childWidth + mPageSpacing;
673            }
674        }
675
676        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
677            setHorizontalScrollBarEnabled(false);
678            updateCurrentPageScroll();
679            setHorizontalScrollBarEnabled(true);
680            mFirstLayout = false;
681        }
682    }
683
684    protected void screenScrolled(int screenCenter) {
685        if (isScrollingIndicatorEnabled()) {
686            updateScrollingIndicator();
687        }
688        boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
689
690        if (mFadeInAdjacentScreens && !isInOverscroll) {
691            for (int i = 0; i < getChildCount(); i++) {
692                View child = getChildAt(i);
693                if (child != null) {
694                    float scrollProgress = getScrollProgress(screenCenter, child, i);
695                    float alpha = 1 - Math.abs(scrollProgress);
696                    child.setAlpha(alpha);
697                }
698            }
699            invalidate();
700        }
701    }
702
703    @Override
704    public void onChildViewAdded(View parent, View child) {
705        // This ensures that when children are added, they get the correct transforms / alphas
706        // in accordance with any scroll effects.
707        mForceScreenScrolled = true;
708        invalidate();
709        invalidateCachedOffsets();
710    }
711
712    @Override
713    public void onChildViewRemoved(View parent, View child) {
714    }
715
716    protected void invalidateCachedOffsets() {
717        int count = getChildCount();
718        if (count == 0) {
719            mChildOffsets = null;
720            mChildRelativeOffsets = null;
721            mChildOffsetsWithLayoutScale = null;
722            return;
723        }
724
725        mChildOffsets = new int[count];
726        mChildRelativeOffsets = new int[count];
727        mChildOffsetsWithLayoutScale = new int[count];
728        for (int i = 0; i < count; i++) {
729            mChildOffsets[i] = -1;
730            mChildRelativeOffsets[i] = -1;
731            mChildOffsetsWithLayoutScale[i] = -1;
732        }
733    }
734
735    protected int getChildOffset(int index) {
736        final boolean isRtl = isLayoutRtl();
737        int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
738                mChildOffsets : mChildOffsetsWithLayoutScale;
739
740        if (childOffsets != null && childOffsets[index] != -1) {
741            return childOffsets[index];
742        } else {
743            if (getChildCount() == 0)
744                return 0;
745
746            final int startIndex = isRtl ? getChildCount() - 1 : 0;
747            final int endIndex = isRtl ? index : index;
748            final int delta = isRtl ? -1 : 1;
749            int offset = getRelativeChildOffset(startIndex);
750            for (int i = startIndex; i != endIndex; i += delta) {
751                offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
752            }
753            if (childOffsets != null) {
754                childOffsets[index] = offset;
755            }
756            return offset;
757        }
758    }
759
760    protected int getRelativeChildOffset(int index) {
761        if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
762            return mChildRelativeOffsets[index];
763        } else {
764            final int padding = getPaddingLeft() + getPaddingRight();
765            final int offset = getPaddingLeft() +
766                    (getMeasuredWidth() - padding - getChildWidth(index)) / 2;
767            if (mChildRelativeOffsets != null) {
768                mChildRelativeOffsets[index] = offset;
769            }
770            return offset;
771        }
772    }
773
774    protected int getScaledMeasuredWidth(View child) {
775        // This functions are called enough times that it actually makes a difference in the
776        // profiler -- so just inline the max() here
777        final int measuredWidth = child.getMeasuredWidth();
778        final int minWidth = mMinimumWidth;
779        final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
780        return (int) (maxWidth * mLayoutScale + 0.5f);
781    }
782
783    protected void getVisiblePages(int[] range) {
784        final boolean isRtl = isLayoutRtl();
785        final int pageCount = getChildCount();
786
787        if (pageCount > 0) {
788            final int screenWidth = getMeasuredWidth();
789            int leftScreen = isRtl ? pageCount - 1 : 0;
790            int rightScreen = 0;
791            int endIndex = isRtl ? 0 : pageCount - 1;
792            int delta = isRtl ? -1 : 1;
793            View currPage = getPageAt(leftScreen);
794            while (leftScreen != endIndex &&
795                    currPage.getX() + currPage.getWidth() -
796                    currPage.getPaddingRight() < getScrollX()) {
797                leftScreen += delta;
798                currPage = getPageAt(leftScreen);
799            }
800            rightScreen = leftScreen;
801            currPage = getPageAt(rightScreen + delta);
802            while (rightScreen != endIndex &&
803                    currPage.getX() - currPage.getPaddingLeft() < getScrollX() + screenWidth) {
804                rightScreen += delta;
805                currPage = getPageAt(rightScreen + delta);
806            }
807            range[0] = Math.min(leftScreen, rightScreen);
808            range[1] = Math.max(leftScreen, rightScreen);
809        } else {
810            range[0] = -1;
811            range[1] = -1;
812        }
813    }
814
815    protected boolean shouldDrawChild(View child) {
816        return child.getAlpha() > 0;
817    }
818
819    @Override
820    protected void dispatchDraw(Canvas canvas) {
821        int halfScreenSize = getMeasuredWidth() / 2;
822        // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
823        // Otherwise it is equal to the scaled overscroll position.
824        int screenCenter = mOverScrollX + halfScreenSize;
825
826        if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
827            // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
828            // set it for the next frame
829            mForceScreenScrolled = false;
830            screenScrolled(screenCenter);
831            mLastScreenCenter = screenCenter;
832        }
833
834        // Find out which screens are visible; as an optimization we only call draw on them
835        final int pageCount = getChildCount();
836        if (pageCount > 0) {
837            getVisiblePages(mTempVisiblePagesRange);
838            final int leftScreen = mTempVisiblePagesRange[0];
839            final int rightScreen = mTempVisiblePagesRange[1];
840            if (leftScreen != -1 && rightScreen != -1) {
841                final long drawingTime = getDrawingTime();
842                // Clip to the bounds
843                canvas.save();
844                canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
845                        getScrollY() + getBottom() - getTop());
846
847                for (int i = getChildCount() - 1; i >= 0; i--) {
848                    final View v = getPageAt(i);
849                    if (mForceDrawAllChildrenNextFrame ||
850                               (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
851                        drawChild(canvas, v, drawingTime);
852                    }
853                }
854                mForceDrawAllChildrenNextFrame = false;
855                canvas.restore();
856            }
857        }
858    }
859
860    @Override
861    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
862        int page = indexToPage(indexOfChild(child));
863        if (page != mCurrentPage || !mScroller.isFinished()) {
864            snapToPage(page);
865            return true;
866        }
867        return false;
868    }
869
870    @Override
871    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
872        int focusablePage;
873        if (mNextPage != INVALID_PAGE) {
874            focusablePage = mNextPage;
875        } else {
876            focusablePage = mCurrentPage;
877        }
878        View v = getPageAt(focusablePage);
879        if (v != null) {
880            return v.requestFocus(direction, previouslyFocusedRect);
881        }
882        return false;
883    }
884
885    @Override
886    public boolean dispatchUnhandledMove(View focused, int direction) {
887        // XXX-RTL: This will be fixed in a future CL
888        if (direction == View.FOCUS_LEFT) {
889            if (getCurrentPage() > 0) {
890                snapToPage(getCurrentPage() - 1);
891                return true;
892            }
893        } else if (direction == View.FOCUS_RIGHT) {
894            if (getCurrentPage() < getPageCount() - 1) {
895                snapToPage(getCurrentPage() + 1);
896                return true;
897            }
898        }
899        return super.dispatchUnhandledMove(focused, direction);
900    }
901
902    @Override
903    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
904        // XXX-RTL: This will be fixed in a future CL
905        if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
906            getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
907        }
908        if (direction == View.FOCUS_LEFT) {
909            if (mCurrentPage > 0) {
910                getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
911            }
912        } else if (direction == View.FOCUS_RIGHT){
913            if (mCurrentPage < getPageCount() - 1) {
914                getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
915            }
916        }
917    }
918
919    /**
920     * If one of our descendant views decides that it could be focused now, only
921     * pass that along if it's on the current page.
922     *
923     * This happens when live folders requery, and if they're off page, they
924     * end up calling requestFocus, which pulls it on page.
925     */
926    @Override
927    public void focusableViewAvailable(View focused) {
928        View current = getPageAt(mCurrentPage);
929        View v = focused;
930        while (true) {
931            if (v == current) {
932                super.focusableViewAvailable(focused);
933                return;
934            }
935            if (v == this) {
936                return;
937            }
938            ViewParent parent = v.getParent();
939            if (parent instanceof View) {
940                v = (View)v.getParent();
941            } else {
942                return;
943            }
944        }
945    }
946
947    /**
948     * {@inheritDoc}
949     */
950    @Override
951    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
952        if (disallowIntercept) {
953            // We need to make sure to cancel our long press if
954            // a scrollable widget takes over touch events
955            final View currentPage = getPageAt(mCurrentPage);
956            currentPage.cancelLongPress();
957        }
958        super.requestDisallowInterceptTouchEvent(disallowIntercept);
959    }
960
961    /**
962     * Return true if a tap at (x, y) should trigger a flip to the previous page.
963     */
964    protected boolean hitsPreviousPage(float x, float y) {
965        if (isLayoutRtl()) {
966            return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
967        } else {
968            return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing);
969        }
970    }
971
972    /**
973     * Return true if a tap at (x, y) should trigger a flip to the next page.
974     */
975    protected boolean hitsNextPage(float x, float y) {
976        if (isLayoutRtl()) {
977            return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing);
978        } else {
979            return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
980        }
981
982    }
983
984    @Override
985    public boolean onInterceptTouchEvent(MotionEvent ev) {
986        /*
987         * This method JUST determines whether we want to intercept the motion.
988         * If we return true, onTouchEvent will be called and we do the actual
989         * scrolling there.
990         */
991        acquireVelocityTrackerAndAddMovement(ev);
992
993        // Skip touch handling if there are no pages to swipe
994        if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
995
996        /*
997         * Shortcut the most recurring case: the user is in the dragging
998         * state and he is moving his finger.  We want to intercept this
999         * motion.
1000         */
1001        final int action = ev.getAction();
1002        if ((action == MotionEvent.ACTION_MOVE) &&
1003                (mTouchState == TOUCH_STATE_SCROLLING)) {
1004            return true;
1005        }
1006
1007        switch (action & MotionEvent.ACTION_MASK) {
1008            case MotionEvent.ACTION_MOVE: {
1009                /*
1010                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1011                 * whether the user has moved far enough from his original down touch.
1012                 */
1013                if (mActivePointerId != INVALID_POINTER) {
1014                    determineScrollingStart(ev);
1015                    break;
1016                }
1017                // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
1018                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
1019                // i.e. fall through to the next case (don't break)
1020                // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
1021                // while it's small- this was causing a crash before we checked for INVALID_POINTER)
1022            }
1023
1024            case MotionEvent.ACTION_DOWN: {
1025                final float x = ev.getX();
1026                final float y = ev.getY();
1027                // Remember location of down touch
1028                mDownMotionX = x;
1029                mLastMotionX = x;
1030                mLastMotionY = y;
1031                mLastMotionXRemainder = 0;
1032                mTotalMotionX = 0;
1033                mActivePointerId = ev.getPointerId(0);
1034                mAllowLongPress = true;
1035
1036                /*
1037                 * If being flinged and user touches the screen, initiate drag;
1038                 * otherwise don't.  mScroller.isFinished should be false when
1039                 * being flinged.
1040                 */
1041                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
1042                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
1043                if (finishedScrolling) {
1044                    mTouchState = TOUCH_STATE_REST;
1045                    mScroller.abortAnimation();
1046                } else {
1047                    mTouchState = TOUCH_STATE_SCROLLING;
1048                }
1049
1050                // check if this can be the beginning of a tap on the side of the pages
1051                // to scroll the current page
1052                if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
1053                    if (getChildCount() > 0) {
1054                        if (hitsPreviousPage(x, y)) {
1055                            mTouchState = TOUCH_STATE_PREV_PAGE;
1056                        } else if (hitsNextPage(x, y)) {
1057                            mTouchState = TOUCH_STATE_NEXT_PAGE;
1058                        }
1059                    }
1060                }
1061                break;
1062            }
1063
1064            case MotionEvent.ACTION_UP:
1065            case MotionEvent.ACTION_CANCEL:
1066                mTouchState = TOUCH_STATE_REST;
1067                mAllowLongPress = false;
1068                mActivePointerId = INVALID_POINTER;
1069                releaseVelocityTracker();
1070                break;
1071
1072            case MotionEvent.ACTION_POINTER_UP:
1073                onSecondaryPointerUp(ev);
1074                releaseVelocityTracker();
1075                break;
1076        }
1077
1078        /*
1079         * The only time we want to intercept motion events is if we are in the
1080         * drag mode.
1081         */
1082        return mTouchState != TOUCH_STATE_REST;
1083    }
1084
1085    protected void determineScrollingStart(MotionEvent ev) {
1086        determineScrollingStart(ev, 1.0f);
1087    }
1088
1089    /*
1090     * Determines if we should change the touch state to start scrolling after the
1091     * user moves their touch point too far.
1092     */
1093    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1094        /*
1095         * Locally do absolute value. mLastMotionX is set to the y value
1096         * of the down event.
1097         */
1098        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1099        if (pointerIndex == -1) {
1100            return;
1101        }
1102        final float x = ev.getX(pointerIndex);
1103        final float y = ev.getY(pointerIndex);
1104        final int xDiff = (int) Math.abs(x - mLastMotionX);
1105        final int yDiff = (int) Math.abs(y - mLastMotionY);
1106
1107        final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
1108        boolean xPaged = xDiff > mPagingTouchSlop;
1109        boolean xMoved = xDiff > touchSlop;
1110        boolean yMoved = yDiff > touchSlop;
1111
1112        if (xMoved || xPaged || yMoved) {
1113            if (mUsePagingTouchSlop ? xPaged : xMoved) {
1114                // Scroll if the user moved far enough along the X axis
1115                mTouchState = TOUCH_STATE_SCROLLING;
1116                mTotalMotionX += Math.abs(mLastMotionX - x);
1117                mLastMotionX = x;
1118                mLastMotionXRemainder = 0;
1119                mTouchX = getScrollX();
1120                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1121                pageBeginMoving();
1122            }
1123            // Either way, cancel any pending longpress
1124            cancelCurrentPageLongPress();
1125        }
1126    }
1127
1128    protected void cancelCurrentPageLongPress() {
1129        if (mAllowLongPress) {
1130            mAllowLongPress = false;
1131            // Try canceling the long press. It could also have been scheduled
1132            // by a distant descendant, so use the mAllowLongPress flag to block
1133            // everything
1134            final View currentPage = getPageAt(mCurrentPage);
1135            if (currentPage != null) {
1136                currentPage.cancelLongPress();
1137            }
1138        }
1139    }
1140
1141    protected float getScrollProgress(int screenCenter, View v, int page) {
1142        final int halfScreenSize = getMeasuredWidth() / 2;
1143
1144        int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
1145        int delta = screenCenter - (getChildOffset(page) -
1146                getRelativeChildOffset(page) + halfScreenSize);
1147
1148        float scrollProgress = delta / (totalDistance * 1.0f);
1149        scrollProgress = Math.min(scrollProgress, 1.0f);
1150        scrollProgress = Math.max(scrollProgress, -1.0f);
1151        return scrollProgress;
1152    }
1153
1154    // This curve determines how the effect of scrolling over the limits of the page dimishes
1155    // as the user pulls further and further from the bounds
1156    private float overScrollInfluenceCurve(float f) {
1157        f -= 1.0f;
1158        return f * f * f + 1.0f;
1159    }
1160
1161    protected void acceleratedOverScroll(float amount) {
1162        int screenSize = getMeasuredWidth();
1163
1164        // We want to reach the max over scroll effect when the user has
1165        // over scrolled half the size of the screen
1166        float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1167
1168        if (f == 0) return;
1169
1170        // Clamp this factor, f, to -1 < f < 1
1171        if (Math.abs(f) >= 1) {
1172            f /= Math.abs(f);
1173        }
1174
1175        int overScrollAmount = (int) Math.round(f * screenSize);
1176        if (amount < 0) {
1177            mOverScrollX = overScrollAmount;
1178            super.scrollTo(0, getScrollY());
1179        } else {
1180            mOverScrollX = mMaxScrollX + overScrollAmount;
1181            super.scrollTo(mMaxScrollX, getScrollY());
1182        }
1183        invalidate();
1184    }
1185
1186    protected void dampedOverScroll(float amount) {
1187        int screenSize = getMeasuredWidth();
1188
1189        float f = (amount / screenSize);
1190
1191        if (f == 0) return;
1192        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
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(OVERSCROLL_DAMP_FACTOR * 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 overScroll(float amount) {
1211        dampedOverScroll(amount);
1212    }
1213
1214    protected float maxOverScroll() {
1215        // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1216        // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1217        float f = 1.0f;
1218        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1219        return OVERSCROLL_DAMP_FACTOR * f;
1220    }
1221
1222    @Override
1223    public boolean onTouchEvent(MotionEvent ev) {
1224        // Skip touch handling if there are no pages to swipe
1225        if (getChildCount() <= 0) return super.onTouchEvent(ev);
1226
1227        acquireVelocityTrackerAndAddMovement(ev);
1228
1229        final int action = ev.getAction();
1230
1231        switch (action & MotionEvent.ACTION_MASK) {
1232        case MotionEvent.ACTION_DOWN:
1233            /*
1234             * If being flinged and user touches, stop the fling. isFinished
1235             * will be false if being flinged.
1236             */
1237            if (!mScroller.isFinished()) {
1238                mScroller.abortAnimation();
1239            }
1240
1241            // Remember where the motion event started
1242            mDownMotionX = mLastMotionX = ev.getX();
1243            mLastMotionXRemainder = 0;
1244            mTotalMotionX = 0;
1245            mActivePointerId = ev.getPointerId(0);
1246            if (mTouchState == TOUCH_STATE_SCROLLING) {
1247                pageBeginMoving();
1248            }
1249            break;
1250
1251        case MotionEvent.ACTION_MOVE:
1252            if (mTouchState == TOUCH_STATE_SCROLLING) {
1253                // Scroll to follow the motion event
1254                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1255                final float x = ev.getX(pointerIndex);
1256                final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1257
1258                mTotalMotionX += Math.abs(deltaX);
1259
1260                // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
1261                // keep the remainder because we are actually testing if we've moved from the last
1262                // scrolled position (which is discrete).
1263                if (Math.abs(deltaX) >= 1.0f) {
1264                    mTouchX += deltaX;
1265                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1266                    if (!mDeferScrollUpdate) {
1267                        scrollBy((int) deltaX, 0);
1268                        if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
1269                    } else {
1270                        invalidate();
1271                    }
1272                    mLastMotionX = x;
1273                    mLastMotionXRemainder = deltaX - (int) deltaX;
1274                } else {
1275                    awakenScrollBars();
1276                }
1277            } else {
1278                determineScrollingStart(ev);
1279            }
1280            break;
1281
1282        case MotionEvent.ACTION_UP:
1283            if (mTouchState == TOUCH_STATE_SCROLLING) {
1284                final int activePointerId = mActivePointerId;
1285                final int pointerIndex = ev.findPointerIndex(activePointerId);
1286                final float x = ev.getX(pointerIndex);
1287                final VelocityTracker velocityTracker = mVelocityTracker;
1288                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1289                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1290                final int deltaX = (int) (x - mDownMotionX);
1291                final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
1292                boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1293                        SIGNIFICANT_MOVE_THRESHOLD;
1294
1295                mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1296
1297                boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1298                        Math.abs(velocityX) > mFlingThresholdVelocity;
1299
1300                // In the case that the page is moved far to one direction and then is flung
1301                // in the opposite direction, we use a threshold to determine whether we should
1302                // just return to the starting page, or if we should skip one further.
1303                boolean returnToOriginalPage = false;
1304                if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1305                        Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1306                    returnToOriginalPage = true;
1307                }
1308
1309                int finalPage;
1310                // We give flings precedence over large moves, which is why we short-circuit our
1311                // test for a large move if a fling has been registered. That is, a large
1312                // move to the left and fling to the right will register as a fling to the right.
1313                final boolean isRtl = isLayoutRtl();
1314                boolean isDeltaXLeft = isRtl ? deltaX > 0 : deltaX < 0;
1315                boolean isVelocityXLeft = isRtl ? velocityX > 0 : velocityX < 0;
1316                if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
1317                        (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
1318                    finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
1319                    snapToPageWithVelocity(finalPage, velocityX);
1320                } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
1321                        (isFling && isVelocityXLeft)) &&
1322                        mCurrentPage < getChildCount() - 1) {
1323                    finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
1324                    snapToPageWithVelocity(finalPage, velocityX);
1325                } else {
1326                    snapToDestination();
1327                }
1328            } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1329                // at this point we have not moved beyond the touch slop
1330                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1331                // we can just page
1332                int nextPage = Math.max(0, mCurrentPage - 1);
1333                if (nextPage != mCurrentPage) {
1334                    snapToPage(nextPage);
1335                } else {
1336                    snapToDestination();
1337                }
1338            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1339                // at this point we have not moved beyond the touch slop
1340                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1341                // we can just page
1342                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1343                if (nextPage != mCurrentPage) {
1344                    snapToPage(nextPage);
1345                } else {
1346                    snapToDestination();
1347                }
1348            } else {
1349                onUnhandledTap(ev);
1350            }
1351            mTouchState = TOUCH_STATE_REST;
1352            mActivePointerId = INVALID_POINTER;
1353            releaseVelocityTracker();
1354            break;
1355
1356        case MotionEvent.ACTION_CANCEL:
1357            if (mTouchState == TOUCH_STATE_SCROLLING) {
1358                snapToDestination();
1359            }
1360            mTouchState = TOUCH_STATE_REST;
1361            mActivePointerId = INVALID_POINTER;
1362            releaseVelocityTracker();
1363            break;
1364
1365        case MotionEvent.ACTION_POINTER_UP:
1366            onSecondaryPointerUp(ev);
1367            break;
1368        }
1369
1370        return true;
1371    }
1372
1373    @Override
1374    public boolean onGenericMotionEvent(MotionEvent event) {
1375        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1376            switch (event.getAction()) {
1377                case MotionEvent.ACTION_SCROLL: {
1378                    // Handle mouse (or ext. device) by shifting the page depending on the scroll
1379                    final float vscroll;
1380                    final float hscroll;
1381                    if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1382                        vscroll = 0;
1383                        hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1384                    } else {
1385                        vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1386                        hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1387                    }
1388                    if (hscroll != 0 || vscroll != 0) {
1389                        boolean isForwardScroll = isLayoutRtl() ? (hscroll < 0 || vscroll < 0)
1390                                                         : (hscroll > 0 || vscroll > 0);
1391                        if (isForwardScroll) {
1392                            scrollRight();
1393                        } else {
1394                            scrollLeft();
1395                        }
1396                        return true;
1397                    }
1398                }
1399            }
1400        }
1401        return super.onGenericMotionEvent(event);
1402    }
1403
1404    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1405        if (mVelocityTracker == null) {
1406            mVelocityTracker = VelocityTracker.obtain();
1407        }
1408        mVelocityTracker.addMovement(ev);
1409    }
1410
1411    private void releaseVelocityTracker() {
1412        if (mVelocityTracker != null) {
1413            mVelocityTracker.recycle();
1414            mVelocityTracker = null;
1415        }
1416    }
1417
1418    private void onSecondaryPointerUp(MotionEvent ev) {
1419        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1420                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1421        final int pointerId = ev.getPointerId(pointerIndex);
1422        if (pointerId == mActivePointerId) {
1423            // This was our active pointer going up. Choose a new
1424            // active pointer and adjust accordingly.
1425            // TODO: Make this decision more intelligent.
1426            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1427            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1428            mLastMotionY = ev.getY(newPointerIndex);
1429            mLastMotionXRemainder = 0;
1430            mActivePointerId = ev.getPointerId(newPointerIndex);
1431            if (mVelocityTracker != null) {
1432                mVelocityTracker.clear();
1433            }
1434        }
1435    }
1436
1437    protected void onUnhandledTap(MotionEvent ev) {}
1438
1439    @Override
1440    public void requestChildFocus(View child, View focused) {
1441        super.requestChildFocus(child, focused);
1442        int page = indexToPage(indexOfChild(child));
1443        if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
1444            snapToPage(page);
1445        }
1446    }
1447
1448    protected int getChildIndexForRelativeOffset(int relativeOffset) {
1449        final boolean isRtl = isLayoutRtl();
1450        final int childCount = getChildCount();
1451        int left;
1452        int right;
1453        final int startIndex = isRtl ? childCount - 1 : 0;
1454        final int endIndex = isRtl ? -1 : childCount;
1455        final int delta = isRtl ? -1 : 1;
1456        for (int i = startIndex; i != endIndex; i += delta) {
1457            left = getRelativeChildOffset(i);
1458            right = (left + getScaledMeasuredWidth(getPageAt(i)));
1459            if (left <= relativeOffset && relativeOffset <= right) {
1460                return i;
1461            }
1462        }
1463        return -1;
1464    }
1465
1466    protected int getChildWidth(int index) {
1467        // This functions are called enough times that it actually makes a difference in the
1468        // profiler -- so just inline the max() here
1469        final int measuredWidth = getPageAt(index).getMeasuredWidth();
1470        final int minWidth = mMinimumWidth;
1471        return (minWidth > measuredWidth) ? minWidth : measuredWidth;
1472    }
1473
1474    int getPageNearestToCenterOfScreen() {
1475        int minDistanceFromScreenCenter = Integer.MAX_VALUE;
1476        int minDistanceFromScreenCenterIndex = -1;
1477        int screenCenter = getScrollX() + (getMeasuredWidth() / 2);
1478        final int childCount = getChildCount();
1479        for (int i = 0; i < childCount; ++i) {
1480            View layout = (View) getPageAt(i);
1481            int childWidth = getScaledMeasuredWidth(layout);
1482            int halfChildWidth = (childWidth / 2);
1483            int childCenter = getChildOffset(i) + halfChildWidth;
1484            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1485            if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1486                minDistanceFromScreenCenter = distanceFromScreenCenter;
1487                minDistanceFromScreenCenterIndex = i;
1488            }
1489        }
1490        return minDistanceFromScreenCenterIndex;
1491    }
1492
1493    protected void snapToDestination() {
1494        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
1495    }
1496
1497    private static class ScrollInterpolator implements Interpolator {
1498        public ScrollInterpolator() {
1499        }
1500
1501        public float getInterpolation(float t) {
1502            t -= 1.0f;
1503            return t*t*t*t*t + 1;
1504        }
1505    }
1506
1507    // We want the duration of the page snap animation to be influenced by the distance that
1508    // the screen has to travel, however, we don't want this duration to be effected in a
1509    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1510    // of travel has on the overall snap duration.
1511    float distanceInfluenceForSnapDuration(float f) {
1512        f -= 0.5f; // center the values about 0.
1513        f *= 0.3f * Math.PI / 2.0f;
1514        return (float) Math.sin(f);
1515    }
1516
1517    protected void snapToPageWithVelocity(int whichPage, int velocity) {
1518        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
1519        int halfScreenSize = getMeasuredWidth() / 2;
1520
1521        if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1522        if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
1523                + getMeasuredWidth() + ", " + getChildWidth(whichPage));
1524        final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1525        int delta = newX - mUnboundedScrollX;
1526        int duration = 0;
1527
1528        if (Math.abs(velocity) < mMinFlingVelocity) {
1529            // If the velocity is low enough, then treat this more as an automatic page advance
1530            // as opposed to an apparent physical response to flinging
1531            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1532            return;
1533        }
1534
1535        // Here we compute a "distance" that will be used in the computation of the overall
1536        // snap duration. This is a function of the actual distance that needs to be traveled;
1537        // we keep this value close to half screen size in order to reduce the variance in snap
1538        // duration as a function of the distance the page needs to travel.
1539        float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
1540        float distance = halfScreenSize + halfScreenSize *
1541                distanceInfluenceForSnapDuration(distanceRatio);
1542
1543        velocity = Math.abs(velocity);
1544        velocity = Math.max(mMinSnapVelocity, velocity);
1545
1546        // we want the page's snap velocity to approximately match the velocity at which the
1547        // user flings, so we scale the duration by a value near to the derivative of the scroll
1548        // interpolator at zero, ie. 5. We use 4 to make it a little slower.
1549        duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1550        duration = Math.min(duration, MAX_PAGE_SNAP_DURATION);
1551
1552        snapToPage(whichPage, delta, duration);
1553    }
1554
1555    protected void snapToPage(int whichPage) {
1556        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1557    }
1558
1559    protected void snapToPage(int whichPage, int duration) {
1560        whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1561
1562        if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1563        if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", "
1564                + getChildWidth(whichPage));
1565        int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1566        final int sX = mUnboundedScrollX;
1567        final int delta = newX - sX;
1568        snapToPage(whichPage, delta, duration);
1569    }
1570
1571    protected void snapToPage(int whichPage, int delta, int duration) {
1572        mNextPage = whichPage;
1573
1574        View focusedChild = getFocusedChild();
1575        if (focusedChild != null && whichPage != mCurrentPage &&
1576                focusedChild == getPageAt(mCurrentPage)) {
1577            focusedChild.clearFocus();
1578        }
1579
1580        pageBeginMoving();
1581        awakenScrollBars(duration);
1582        if (duration == 0) {
1583            duration = Math.abs(delta);
1584        }
1585
1586        if (!mScroller.isFinished()) mScroller.abortAnimation();
1587        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
1588
1589        // Load associated pages immediately if someone else is handling the scroll, otherwise defer
1590        // loading associated pages until the scroll settles
1591        if (mDeferScrollUpdate) {
1592            loadAssociatedPages(mNextPage);
1593        } else {
1594            mDeferLoadAssociatedPagesUntilScrollCompletes = true;
1595        }
1596        notifyPageSwitchListener();
1597        invalidate();
1598    }
1599
1600    public void scrollLeft() {
1601        if (mScroller.isFinished()) {
1602            if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
1603        } else {
1604            if (mNextPage > 0) snapToPage(mNextPage - 1);
1605        }
1606    }
1607
1608    public void scrollRight() {
1609        if (mScroller.isFinished()) {
1610            if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
1611        } else {
1612            if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
1613        }
1614    }
1615
1616    public int getPageForView(View v) {
1617        int result = -1;
1618        if (v != null) {
1619            ViewParent vp = v.getParent();
1620            int count = getChildCount();
1621            for (int i = 0; i < count; i++) {
1622                if (vp == getPageAt(i)) {
1623                    return i;
1624                }
1625            }
1626        }
1627        return result;
1628    }
1629
1630    /**
1631     * @return True is long presses are still allowed for the current touch
1632     */
1633    public boolean allowLongPress() {
1634        return mAllowLongPress;
1635    }
1636
1637    /**
1638     * Set true to allow long-press events to be triggered, usually checked by
1639     * {@link Launcher} to accept or block dpad-initiated long-presses.
1640     */
1641    public void setAllowLongPress(boolean allowLongPress) {
1642        mAllowLongPress = allowLongPress;
1643    }
1644
1645    public static class SavedState extends BaseSavedState {
1646        int currentPage = -1;
1647
1648        SavedState(Parcelable superState) {
1649            super(superState);
1650        }
1651
1652        private SavedState(Parcel in) {
1653            super(in);
1654            currentPage = in.readInt();
1655        }
1656
1657        @Override
1658        public void writeToParcel(Parcel out, int flags) {
1659            super.writeToParcel(out, flags);
1660            out.writeInt(currentPage);
1661        }
1662
1663        public static final Parcelable.Creator<SavedState> CREATOR =
1664                new Parcelable.Creator<SavedState>() {
1665            public SavedState createFromParcel(Parcel in) {
1666                return new SavedState(in);
1667            }
1668
1669            public SavedState[] newArray(int size) {
1670                return new SavedState[size];
1671            }
1672        };
1673    }
1674
1675    protected void loadAssociatedPages(int page) {
1676        loadAssociatedPages(page, false);
1677    }
1678    protected void loadAssociatedPages(int page, boolean immediateAndOnly) {
1679        if (mContentIsRefreshable) {
1680            final int count = getChildCount();
1681            if (page < count) {
1682                int lowerPageBound = getAssociatedLowerPageBound(page);
1683                int upperPageBound = getAssociatedUpperPageBound(page);
1684                if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/"
1685                        + upperPageBound);
1686                // First, clear any pages that should no longer be loaded
1687                for (int i = 0; i < count; ++i) {
1688                    Page layout = (Page) getPageAt(i);
1689                    if ((i < lowerPageBound) || (i > upperPageBound)) {
1690                        if (layout.getPageChildCount() > 0) {
1691                            layout.removeAllViewsOnPage();
1692                        }
1693                        mDirtyPageContent.set(i, true);
1694                    }
1695                }
1696                // Next, load any new pages
1697                for (int i = 0; i < count; ++i) {
1698                    if ((i != page) && immediateAndOnly) {
1699                        continue;
1700                    }
1701                    if (lowerPageBound <= i && i <= upperPageBound) {
1702                        if (mDirtyPageContent.get(i)) {
1703                            syncPageItems(i, (i == page) && immediateAndOnly);
1704                            mDirtyPageContent.set(i, false);
1705                        }
1706                    }
1707                }
1708            }
1709        }
1710    }
1711
1712    protected int getAssociatedLowerPageBound(int page) {
1713        return Math.max(0, page - 1);
1714    }
1715    protected int getAssociatedUpperPageBound(int page) {
1716        final int count = getChildCount();
1717        return Math.min(page + 1, count - 1);
1718    }
1719
1720    /**
1721     * This method is called ONLY to synchronize the number of pages that the paged view has.
1722     * To actually fill the pages with information, implement syncPageItems() below.  It is
1723     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
1724     * and therefore, individual page items do not need to be updated in this method.
1725     */
1726    public abstract void syncPages();
1727
1728    /**
1729     * This method is called to synchronize the items that are on a particular page.  If views on
1730     * the page can be reused, then they should be updated within this method.
1731     */
1732    public abstract void syncPageItems(int page, boolean immediate);
1733
1734    protected void invalidatePageData() {
1735        invalidatePageData(-1, false);
1736    }
1737    protected void invalidatePageData(int currentPage) {
1738        invalidatePageData(currentPage, false);
1739    }
1740    protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
1741        if (!mIsDataReady) {
1742            return;
1743        }
1744
1745        if (mContentIsRefreshable) {
1746            // Force all scrolling-related behavior to end
1747            mScroller.forceFinished(true);
1748            mNextPage = INVALID_PAGE;
1749
1750            // Update all the pages
1751            syncPages();
1752
1753            // We must force a measure after we've loaded the pages to update the content width and
1754            // to determine the full scroll width
1755            measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
1756                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
1757
1758            // Set a new page as the current page if necessary
1759            if (currentPage > -1) {
1760                setCurrentPage(Math.min(getPageCount() - 1, currentPage));
1761            }
1762
1763            // Mark each of the pages as dirty
1764            final int count = getChildCount();
1765            mDirtyPageContent.clear();
1766            for (int i = 0; i < count; ++i) {
1767                mDirtyPageContent.add(true);
1768            }
1769
1770            // Load any pages that are necessary for the current window of views
1771            loadAssociatedPages(mCurrentPage, immediateAndOnly);
1772            requestLayout();
1773        }
1774    }
1775
1776    protected View getScrollingIndicator() {
1777        // We use mHasScrollIndicator to prevent future lookups if there is no sibling indicator
1778        // found
1779        if (mHasScrollIndicator && mScrollIndicator == null) {
1780            ViewGroup parent = (ViewGroup) getParent();
1781            if (parent != null) {
1782                mScrollIndicator = (View) (parent.findViewById(R.id.paged_view_indicator));
1783                mHasScrollIndicator = mScrollIndicator != null;
1784                if (mHasScrollIndicator) {
1785                    mScrollIndicator.setVisibility(View.VISIBLE);
1786                }
1787            }
1788        }
1789        return mScrollIndicator;
1790    }
1791
1792    protected boolean isScrollingIndicatorEnabled() {
1793        return true;
1794    }
1795
1796    Runnable hideScrollingIndicatorRunnable = new Runnable() {
1797        @Override
1798        public void run() {
1799            hideScrollingIndicator(false);
1800        }
1801    };
1802    protected void flashScrollingIndicator(boolean animated) {
1803        removeCallbacks(hideScrollingIndicatorRunnable);
1804        showScrollingIndicator(!animated);
1805        postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
1806    }
1807
1808    protected void showScrollingIndicator(boolean immediately) {
1809        mShouldShowScrollIndicator = true;
1810        mShouldShowScrollIndicatorImmediately = true;
1811        if (getChildCount() <= 1) return;
1812        if (!isScrollingIndicatorEnabled()) return;
1813
1814        mShouldShowScrollIndicator = false;
1815        getScrollingIndicator();
1816        if (mScrollIndicator != null) {
1817            // Fade the indicator in
1818            updateScrollingIndicatorPosition();
1819            mScrollIndicator.setVisibility(View.VISIBLE);
1820            cancelScrollingIndicatorAnimations();
1821            if (immediately || mScrollingPaused) {
1822                mScrollIndicator.setAlpha(1f);
1823            } else {
1824                mScrollIndicatorAnimator = LauncherAnimUtils.ofFloat(mScrollIndicator, "alpha", 1f);
1825                mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
1826                mScrollIndicatorAnimator.start();
1827            }
1828        }
1829    }
1830
1831    protected void cancelScrollingIndicatorAnimations() {
1832        if (mScrollIndicatorAnimator != null) {
1833            mScrollIndicatorAnimator.cancel();
1834        }
1835    }
1836
1837    protected void hideScrollingIndicator(boolean immediately) {
1838        if (getChildCount() <= 1) return;
1839        if (!isScrollingIndicatorEnabled()) return;
1840
1841        getScrollingIndicator();
1842        if (mScrollIndicator != null) {
1843            // Fade the indicator out
1844            updateScrollingIndicatorPosition();
1845            cancelScrollingIndicatorAnimations();
1846            if (immediately || mScrollingPaused) {
1847                mScrollIndicator.setVisibility(View.INVISIBLE);
1848                mScrollIndicator.setAlpha(0f);
1849            } else {
1850                mScrollIndicatorAnimator = LauncherAnimUtils.ofFloat(mScrollIndicator, "alpha", 0f);
1851                mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
1852                mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
1853                    private boolean cancelled = false;
1854                    @Override
1855                    public void onAnimationCancel(android.animation.Animator animation) {
1856                        cancelled = true;
1857                    }
1858                    @Override
1859                    public void onAnimationEnd(Animator animation) {
1860                        if (!cancelled) {
1861                            mScrollIndicator.setVisibility(View.INVISIBLE);
1862                        }
1863                    }
1864                });
1865                mScrollIndicatorAnimator.start();
1866            }
1867        }
1868    }
1869
1870    /**
1871     * To be overridden by subclasses to determine whether the scroll indicator should stretch to
1872     * fill its space on the track or not.
1873     */
1874    protected boolean hasElasticScrollIndicator() {
1875        return true;
1876    }
1877
1878    private void updateScrollingIndicator() {
1879        if (getChildCount() <= 1) return;
1880        if (!isScrollingIndicatorEnabled()) return;
1881
1882        getScrollingIndicator();
1883        if (mScrollIndicator != null) {
1884            updateScrollingIndicatorPosition();
1885        }
1886        if (mShouldShowScrollIndicator) {
1887            showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
1888        }
1889    }
1890
1891    private void updateScrollingIndicatorPosition() {
1892        final boolean isRtl = isLayoutRtl();
1893        if (!isScrollingIndicatorEnabled()) return;
1894        if (mScrollIndicator == null) return;
1895        int numPages = getChildCount();
1896        int pageWidth = getMeasuredWidth();
1897        int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
1898        int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
1899                mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
1900
1901        float scrollPos = isRtl ? mMaxScrollX - getScrollX() : getScrollX();
1902        float offset = Math.max(0f, Math.min(1f, (float) scrollPos / mMaxScrollX));
1903        if (isRtl) {
1904            offset = 1f - offset;
1905        }
1906        int indicatorSpace = trackWidth / numPages;
1907        int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
1908        if (hasElasticScrollIndicator()) {
1909            if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
1910                mScrollIndicator.getLayoutParams().width = indicatorSpace;
1911                mScrollIndicator.requestLayout();
1912            }
1913        } else {
1914            int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
1915            indicatorPos += indicatorCenterOffset;
1916        }
1917        mScrollIndicator.setTranslationX(indicatorPos);
1918    }
1919
1920    public void showScrollIndicatorTrack() {
1921    }
1922
1923    public void hideScrollIndicatorTrack() {
1924    }
1925
1926    /* Accessibility */
1927    @Override
1928    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1929        super.onInitializeAccessibilityNodeInfo(info);
1930        info.setScrollable(getPageCount() > 1);
1931        if (getCurrentPage() < getPageCount() - 1) {
1932            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1933        }
1934        if (getCurrentPage() > 0) {
1935            info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1936        }
1937    }
1938
1939    @Override
1940    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1941        super.onInitializeAccessibilityEvent(event);
1942        event.setScrollable(true);
1943        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
1944            event.setFromIndex(mCurrentPage);
1945            event.setToIndex(mCurrentPage);
1946            event.setItemCount(getChildCount());
1947        }
1948    }
1949
1950    @Override
1951    public boolean performAccessibilityAction(int action, Bundle arguments) {
1952        if (super.performAccessibilityAction(action, arguments)) {
1953            return true;
1954        }
1955        switch (action) {
1956            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
1957                if (getCurrentPage() < getPageCount() - 1) {
1958                    scrollRight();
1959                    return true;
1960                }
1961            } break;
1962            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
1963                if (getCurrentPage() > 0) {
1964                    scrollLeft();
1965                    return true;
1966                }
1967            } break;
1968        }
1969        return false;
1970    }
1971
1972    protected String getCurrentPageDescription() {
1973        return String.format(getContext().getString(R.string.default_scroll_format),
1974                 getNextPage() + 1, getChildCount());
1975    }
1976
1977    @Override
1978    public boolean onHoverEvent(android.view.MotionEvent event) {
1979        return true;
1980    }
1981}
1982