PagedView.java revision 73aa9755d3db1a76e9de0f55271ef5984d78ef6f
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 java.util.ArrayList;
20import java.util.HashMap;
21
22import android.content.Context;
23import android.content.res.TypedArray;
24import android.graphics.Bitmap;
25import android.graphics.Canvas;
26import android.graphics.Rect;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.util.AttributeSet;
30import android.view.ActionMode;
31import android.view.MotionEvent;
32import android.view.VelocityTracker;
33import android.view.View;
34import android.view.ViewConfiguration;
35import android.view.ViewGroup;
36import android.view.ViewParent;
37import android.view.animation.Animation;
38import android.view.animation.AnimationUtils;
39import android.view.animation.Interpolator;
40import android.view.animation.Animation.AnimationListener;
41import android.widget.Checkable;
42import android.widget.Scroller;
43
44import com.android.launcher.R;
45
46/**
47 * An abstraction of the original Workspace which supports browsing through a
48 * sequential list of "pages"
49 */
50public abstract class PagedView extends ViewGroup {
51    private static final String TAG = "PagedView";
52    protected static final int INVALID_PAGE = -1;
53
54    // the min drag distance for a fling to register, to prevent random page shifts
55    private static final int MIN_LENGTH_FOR_FLING = 25;
56    // The min drag distance to trigger a page shift (regardless of velocity)
57    private static final int MIN_LENGTH_FOR_MOVE = 200;
58
59    private static final int PAGE_SNAP_ANIMATION_DURATION = 550;
60    protected static final float NANOTIME_DIV = 1000000000.0f;
61
62    private static final float OVERSCROLL_DAMP_FACTOR = 0.08f;
63    private static final int MINIMUM_SNAP_VELOCITY = 2200;
64    private static final int MIN_FLING_VELOCITY = 250;
65
66    // the velocity at which a fling gesture will cause us to snap to the next page
67    protected int mSnapVelocity = 500;
68
69    protected float mSmoothingTime;
70    protected float mTouchX;
71
72    protected boolean mFirstLayout = true;
73
74    protected int mCurrentPage;
75    protected int mNextPage = INVALID_PAGE;
76    protected int mMaxScrollX;
77    protected Scroller mScroller;
78    private VelocityTracker mVelocityTracker;
79
80    private float mDownMotionX;
81    protected float mLastMotionX;
82    protected float mLastMotionY;
83    private int mLastScreenCenter = -1;
84
85    protected final static int TOUCH_STATE_REST = 0;
86    protected final static int TOUCH_STATE_SCROLLING = 1;
87    protected final static int TOUCH_STATE_PREV_PAGE = 2;
88    protected final static int TOUCH_STATE_NEXT_PAGE = 3;
89    protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
90
91    protected int mTouchState = TOUCH_STATE_REST;
92
93    protected OnLongClickListener mLongClickListener;
94
95    protected boolean mAllowLongPress = true;
96
97    protected int mTouchSlop;
98    private int mPagingTouchSlop;
99    private int mMaximumVelocity;
100    protected int mPageSpacing;
101    protected int mPageLayoutPaddingTop;
102    protected int mPageLayoutPaddingBottom;
103    protected int mPageLayoutPaddingLeft;
104    protected int mPageLayoutPaddingRight;
105    protected int mPageLayoutWidthGap;
106    protected int mPageLayoutHeightGap;
107    protected int mCellCountX;
108    protected int mCellCountY;
109    protected boolean mCenterPagesVertically;
110    protected boolean mAllowOverScroll = true;
111    protected int mUnboundedScrollX;
112
113    protected static final int INVALID_POINTER = -1;
114
115    protected int mActivePointerId = INVALID_POINTER;
116
117    private PageSwitchListener mPageSwitchListener;
118
119    private ArrayList<Boolean> mDirtyPageContent;
120    private boolean mDirtyPageAlpha;
121
122    // choice modes
123    protected static final int CHOICE_MODE_NONE = 0;
124    protected static final int CHOICE_MODE_SINGLE = 1;
125    // Multiple selection mode is not supported by all Launcher actions atm
126    protected static final int CHOICE_MODE_MULTIPLE = 2;
127
128    protected int mChoiceMode;
129    private ActionMode mActionMode;
130
131    protected PagedViewIconCache mPageViewIconCache;
132
133    // If true, syncPages and syncPageItems will be called to refresh pages
134    protected boolean mContentIsRefreshable = true;
135
136    // If true, modify alpha of neighboring pages as user scrolls left/right
137    protected boolean mFadeInAdjacentScreens = true;
138
139    // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
140    // to switch to a new page
141    protected boolean mUsePagingTouchSlop = true;
142
143    // If true, the subclass should directly update mScrollX itself in its computeScroll method
144    // (SmoothPagedView does this)
145    protected boolean mDeferScrollUpdate = false;
146
147    protected boolean mIsPageMoving = false;
148
149    /**
150     * Simple cache mechanism for PagedViewIcon outlines.
151     */
152    class PagedViewIconCache {
153        private final HashMap<Object, Bitmap> iconOutlineCache = new HashMap<Object, Bitmap>();
154
155        public void clear() {
156            iconOutlineCache.clear();
157        }
158        public void addOutline(Object key, Bitmap b) {
159            iconOutlineCache.put(key, b);
160        }
161        public void removeOutline(Object key) {
162            if (iconOutlineCache.containsKey(key)) {
163                iconOutlineCache.remove(key);
164            }
165        }
166        public Bitmap getOutline(Object key) {
167            return iconOutlineCache.get(key);
168        }
169    }
170
171    public interface PageSwitchListener {
172        void onPageSwitch(View newPage, int newPageIndex);
173    }
174
175    public PagedView(Context context) {
176        this(context, null);
177    }
178
179    public PagedView(Context context, AttributeSet attrs) {
180        this(context, attrs, 0);
181    }
182
183    public PagedView(Context context, AttributeSet attrs, int defStyle) {
184        super(context, attrs, defStyle);
185        mChoiceMode = CHOICE_MODE_NONE;
186
187        TypedArray a = context.obtainStyledAttributes(attrs,
188                R.styleable.PagedView, defStyle, 0);
189        mPageSpacing = a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0);
190        mPageLayoutPaddingTop = a.getDimensionPixelSize(
191                R.styleable.PagedView_pageLayoutPaddingTop, 10);
192        mPageLayoutPaddingBottom = a.getDimensionPixelSize(
193                R.styleable.PagedView_pageLayoutPaddingBottom, 10);
194        mPageLayoutPaddingLeft = a.getDimensionPixelSize(
195                R.styleable.PagedView_pageLayoutPaddingLeft, 10);
196        mPageLayoutPaddingRight = a.getDimensionPixelSize(
197                R.styleable.PagedView_pageLayoutPaddingRight, 10);
198        mPageLayoutWidthGap = a.getDimensionPixelSize(
199                R.styleable.PagedView_pageLayoutWidthGap, -1);
200        mPageLayoutHeightGap = a.getDimensionPixelSize(
201                R.styleable.PagedView_pageLayoutHeightGap, -1);
202        a.recycle();
203
204        setHapticFeedbackEnabled(false);
205        init();
206    }
207
208    /**
209     * Initializes various states for this workspace.
210     */
211    protected void init() {
212        mDirtyPageContent = new ArrayList<Boolean>();
213        mDirtyPageContent.ensureCapacity(32);
214        mPageViewIconCache = new PagedViewIconCache();
215        mScroller = new Scroller(getContext(), new ScrollInterpolator());
216        mCurrentPage = 0;
217        mCenterPagesVertically = true;
218
219        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
220        mTouchSlop = configuration.getScaledTouchSlop();
221        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
222        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
223    }
224
225    public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
226        mPageSwitchListener = pageSwitchListener;
227        if (mPageSwitchListener != null) {
228            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
229        }
230    }
231
232    /**
233     * Returns the index of the currently displayed page.
234     *
235     * @return The index of the currently displayed page.
236     */
237    int getCurrentPage() {
238        return mCurrentPage;
239    }
240
241    int getPageCount() {
242        return getChildCount();
243    }
244
245    View getPageAt(int index) {
246        return getChildAt(index);
247    }
248
249    int getScrollWidth() {
250        return getWidth();
251    }
252
253    /**
254     * Updates the scroll of the current page immediately to its final scroll position.  We use this
255     * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
256     * the previous tab page.
257     */
258    protected void updateCurrentPageScroll() {
259        int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
260        scrollTo(newX, 0);
261        mScroller.setFinalX(newX);
262    }
263
264    /**
265     * Sets the current page.
266     */
267    void setCurrentPage(int currentPage) {
268        if (!mScroller.isFinished()) {
269            mScroller.abortAnimation();
270        }
271        if (getChildCount() == 0 || currentPage == mCurrentPage) {
272            return;
273        }
274
275        mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
276        updateCurrentPageScroll();
277
278        invalidate();
279        notifyPageSwitchListener();
280    }
281
282    protected void notifyPageSwitchListener() {
283        if (mPageSwitchListener != null) {
284            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
285        }
286    }
287
288    private void pageBeginMoving() {
289        mIsPageMoving = true;
290        onPageBeginMoving();
291    }
292
293    private void pageEndMoving() {
294        onPageEndMoving();
295        mIsPageMoving = false;
296    }
297
298    // a method that subclasses can override to add behavior
299    protected void onPageBeginMoving() {
300    }
301
302    // a method that subclasses can override to add behavior
303    protected void onPageEndMoving() {
304    }
305
306    /**
307     * Registers the specified listener on each page contained in this workspace.
308     *
309     * @param l The listener used to respond to long clicks.
310     */
311    @Override
312    public void setOnLongClickListener(OnLongClickListener l) {
313        mLongClickListener = l;
314        final int count = getPageCount();
315        for (int i = 0; i < count; i++) {
316            getPageAt(i).setOnLongClickListener(l);
317        }
318    }
319
320    @Override
321    public void scrollBy(int x, int y) {
322        scrollTo(mUnboundedScrollX + x, mScrollY + y);
323    }
324
325    @Override
326    public void scrollTo(int x, int y) {
327        mUnboundedScrollX = x;
328
329        if (x < 0) {
330            super.scrollTo(0, y);
331            if (mAllowOverScroll) {
332                overScroll(x);
333            }
334        } else if (x > mMaxScrollX) {
335            super.scrollTo(mMaxScrollX, y);
336            if (mAllowOverScroll) {
337                overScroll(x - mMaxScrollX);
338            }
339        } else {
340            super.scrollTo(x, y);
341        }
342
343        mTouchX = x;
344        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
345    }
346
347    // we moved this functionality to a helper function so SmoothPagedView can reuse it
348    protected boolean computeScrollHelper() {
349        if (mScroller.computeScrollOffset()) {
350            mDirtyPageAlpha = true;
351            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
352            invalidate();
353            return true;
354        } else if (mNextPage != INVALID_PAGE) {
355            mDirtyPageAlpha = true;
356            mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
357            mNextPage = INVALID_PAGE;
358            notifyPageSwitchListener();
359            // We don't want to trigger a page end moving unless the page has settled
360            // and the user has stopped scrolling
361            if (mTouchState == TOUCH_STATE_REST) {
362                pageEndMoving();
363            }
364            return true;
365        }
366        return false;
367    }
368
369    @Override
370    public void computeScroll() {
371        computeScrollHelper();
372    }
373
374    @Override
375    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
376        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
377        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
378        if (widthMode != MeasureSpec.EXACTLY) {
379            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
380        }
381
382        /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
383         * of the All apps view on XLarge displays to not take up more space then it needs. Width
384         * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
385         * each page to have the same width.
386         */
387        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
388        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
389        int maxChildHeight = 0;
390
391        final int verticalPadding = mPaddingTop + mPaddingBottom;
392
393        // The children are given the same width and height as the workspace
394        // unless they were set to WRAP_CONTENT
395        final int childCount = getChildCount();
396        for (int i = 0; i < childCount; i++) {
397            // disallowing padding in paged view (just pass 0)
398            final View child = getChildAt(i);
399            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
400
401            int childWidthMode;
402            if (lp.width == LayoutParams.WRAP_CONTENT) {
403                childWidthMode = MeasureSpec.AT_MOST;
404            } else {
405                childWidthMode = MeasureSpec.EXACTLY;
406            }
407
408            int childHeightMode;
409            if (lp.height == LayoutParams.WRAP_CONTENT) {
410                childHeightMode = MeasureSpec.AT_MOST;
411            } else {
412                childHeightMode = MeasureSpec.EXACTLY;
413            }
414
415            final int childWidthMeasureSpec =
416                MeasureSpec.makeMeasureSpec(widthSize, childWidthMode);
417            final int childHeightMeasureSpec =
418                MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
419
420            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
421            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
422        }
423
424        if (heightMode == MeasureSpec.AT_MOST) {
425            heightSize = maxChildHeight + verticalPadding;
426        }
427        if (childCount > 0) {
428            mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
429        } else {
430            mMaxScrollX = 0;
431        }
432
433        setMeasuredDimension(widthSize, heightSize);
434    }
435
436    @Override
437    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
438        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
439            setHorizontalScrollBarEnabled(false);
440            int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
441            scrollTo(newX, 0);
442            mScroller.setFinalX(newX);
443            setHorizontalScrollBarEnabled(true);
444            mFirstLayout = false;
445        }
446
447        final int verticalPadding = mPaddingTop + mPaddingBottom;
448        final int childCount = getChildCount();
449        int childLeft = 0;
450        if (childCount > 0) {
451            childLeft = getRelativeChildOffset(0);
452        }
453
454        for (int i = 0; i < childCount; i++) {
455            final View child = getChildAt(i);
456            if (child.getVisibility() != View.GONE) {
457                final int childWidth = child.getMeasuredWidth();
458                final int childHeight = child.getMeasuredHeight();
459                int childTop = mPaddingTop;
460                if (mCenterPagesVertically) {
461                    childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
462                }
463                child.layout(childLeft, childTop,
464                        childLeft + childWidth, childTop + childHeight);
465                childLeft += childWidth + mPageSpacing;
466            }
467        }
468    }
469
470    protected void updateAdjacentPagesAlpha() {
471        if (mFadeInAdjacentScreens) {
472            if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) {
473                int halfScreenSize = getMeasuredWidth() / 2;
474                int screenCenter = mScrollX + halfScreenSize;
475                final int childCount = getChildCount();
476                for (int i = 0; i < childCount; ++i) {
477                    View layout = (View) getChildAt(i);
478                    int childWidth = layout.getMeasuredWidth();
479                    int halfChildWidth = (childWidth / 2);
480                    int childCenter = getChildOffset(i) + halfChildWidth;
481
482                    // On the first layout, we may not have a width nor a proper offset, so for now
483                    // we should just assume full page width (and calculate the offset according to
484                    // that).
485                    if (childWidth <= 0) {
486                        childWidth = getMeasuredWidth();
487                        childCenter = (i * childWidth) + (childWidth / 2);
488                    }
489
490                    int d = halfChildWidth;
491                    int distanceFromScreenCenter = childCenter - screenCenter;
492                    if (distanceFromScreenCenter > 0) {
493                        if (i > 0) {
494                            d += getChildAt(i - 1).getMeasuredWidth() / 2;
495                        }
496                    } else {
497                        if (i < childCount - 1) {
498                            d += getChildAt(i + 1).getMeasuredWidth() / 2;
499                        }
500                    }
501                    d += mPageSpacing;
502
503                    // Preventing potential divide-by-zero
504                    d = Math.max(1, d);
505
506                    float dimAlpha = (float) (Math.abs(distanceFromScreenCenter)) / d;
507                    dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha)));
508                    float alpha = 1.0f - dimAlpha;
509
510                    if (alpha < ALPHA_QUANTIZE_LEVEL) {
511                        alpha = 0.0f;
512                    } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) {
513                        alpha = 1.0f;
514                    }
515
516                    if (Float.compare(alpha, layout.getAlpha()) != 0) {
517                        layout.setAlpha(alpha);
518                    }
519                }
520                mDirtyPageAlpha = false;
521            }
522        }
523    }
524
525    protected void screenScrolled(int screenCenter) {
526    }
527
528    @Override
529    protected void dispatchDraw(Canvas canvas) {
530        int halfScreenSize = getMeasuredWidth() / 2;
531        int screenCenter = mScrollX + halfScreenSize;
532
533        if (screenCenter != mLastScreenCenter) {
534            screenScrolled(screenCenter);
535            updateAdjacentPagesAlpha();
536            mLastScreenCenter = screenCenter;
537        }
538
539        // Find out which screens are visible; as an optimization we only call draw on them
540        // As an optimization, this code assumes that all pages have the same width as the 0th
541        // page.
542        final int pageCount = getChildCount();
543        if (pageCount > 0) {
544            final int pageWidth = getChildAt(0).getMeasuredWidth();
545            final int screenWidth = getMeasuredWidth();
546            int x = getRelativeChildOffset(0) + pageWidth;
547            int leftScreen = 0;
548            int rightScreen = 0;
549            while (x <= mScrollX) {
550                leftScreen++;
551                x += pageWidth + mPageSpacing;
552                // replace above line with this if you don't assume all pages have same width as 0th
553                // page:
554                // x += getChildAt(leftScreen).getMeasuredWidth();
555            }
556            rightScreen = leftScreen;
557            while (x < mScrollX + screenWidth) {
558                rightScreen++;
559                x += pageWidth + mPageSpacing;
560                // replace above line with this if you don't assume all pages have same width as 0th
561                // page:
562                //if (rightScreen < pageCount) {
563                //    x += getChildAt(rightScreen).getMeasuredWidth();
564                //}
565            }
566            rightScreen = Math.min(getChildCount() - 1, rightScreen);
567
568            final long drawingTime = getDrawingTime();
569            for (int i = leftScreen; i <= rightScreen; i++) {
570                drawChild(canvas, getChildAt(i), drawingTime);
571            }
572        }
573    }
574
575    @Override
576    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
577        int page = indexOfChild(child);
578        if (page != mCurrentPage || !mScroller.isFinished()) {
579            snapToPage(page);
580            return true;
581        }
582        return false;
583    }
584
585    @Override
586    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
587        int focusablePage;
588        if (mNextPage != INVALID_PAGE) {
589            focusablePage = mNextPage;
590        } else {
591            focusablePage = mCurrentPage;
592        }
593        View v = getPageAt(focusablePage);
594        if (v != null) {
595            v.requestFocus(direction, previouslyFocusedRect);
596        }
597        return false;
598    }
599
600    @Override
601    public boolean dispatchUnhandledMove(View focused, int direction) {
602        if (direction == View.FOCUS_LEFT) {
603            if (getCurrentPage() > 0) {
604                snapToPage(getCurrentPage() - 1);
605                return true;
606            }
607        } else if (direction == View.FOCUS_RIGHT) {
608            if (getCurrentPage() < getPageCount() - 1) {
609                snapToPage(getCurrentPage() + 1);
610                return true;
611            }
612        }
613        return super.dispatchUnhandledMove(focused, direction);
614    }
615
616    @Override
617    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
618        if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
619            getPageAt(mCurrentPage).addFocusables(views, direction);
620        }
621        if (direction == View.FOCUS_LEFT) {
622            if (mCurrentPage > 0) {
623                getPageAt(mCurrentPage - 1).addFocusables(views, direction);
624            }
625        } else if (direction == View.FOCUS_RIGHT){
626            if (mCurrentPage < getPageCount() - 1) {
627                getPageAt(mCurrentPage + 1).addFocusables(views, direction);
628            }
629        }
630    }
631
632    /**
633     * If one of our descendant views decides that it could be focused now, only
634     * pass that along if it's on the current page.
635     *
636     * This happens when live folders requery, and if they're off page, they
637     * end up calling requestFocus, which pulls it on page.
638     */
639    @Override
640    public void focusableViewAvailable(View focused) {
641        View current = getPageAt(mCurrentPage);
642        View v = focused;
643        while (true) {
644            if (v == current) {
645                super.focusableViewAvailable(focused);
646                return;
647            }
648            if (v == this) {
649                return;
650            }
651            ViewParent parent = v.getParent();
652            if (parent instanceof View) {
653                v = (View)v.getParent();
654            } else {
655                return;
656            }
657        }
658    }
659
660    /**
661     * {@inheritDoc}
662     */
663    @Override
664    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
665        if (disallowIntercept) {
666            // We need to make sure to cancel our long press if
667            // a scrollable widget takes over touch events
668            final View currentPage = getChildAt(mCurrentPage);
669            currentPage.cancelLongPress();
670        }
671        super.requestDisallowInterceptTouchEvent(disallowIntercept);
672    }
673
674    @Override
675    public boolean onInterceptTouchEvent(MotionEvent ev) {
676        /*
677         * This method JUST determines whether we want to intercept the motion.
678         * If we return true, onTouchEvent will be called and we do the actual
679         * scrolling there.
680         */
681
682        // Skip touch handling if there are no pages to swipe
683        if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
684
685        /*
686         * Shortcut the most recurring case: the user is in the dragging
687         * state and he is moving his finger.  We want to intercept this
688         * motion.
689         */
690        final int action = ev.getAction();
691        if ((action == MotionEvent.ACTION_MOVE) &&
692                (mTouchState == TOUCH_STATE_SCROLLING)) {
693            return true;
694        }
695
696        switch (action & MotionEvent.ACTION_MASK) {
697            case MotionEvent.ACTION_MOVE: {
698                /*
699                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
700                 * whether the user has moved far enough from his original down touch.
701                 */
702                if (mActivePointerId != INVALID_POINTER) {
703                    determineScrollingStart(ev);
704                    break;
705                }
706                // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
707                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
708                // i.e. fall through to the next case (don't break)
709                // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
710                // while it's small- this was causing a crash before we checked for INVALID_POINTER)
711            }
712
713            case MotionEvent.ACTION_DOWN: {
714                final float x = ev.getX();
715                final float y = ev.getY();
716                // Remember location of down touch
717                mDownMotionX = x;
718                mLastMotionX = x;
719                mLastMotionY = y;
720                mActivePointerId = ev.getPointerId(0);
721                mAllowLongPress = true;
722
723                /*
724                 * If being flinged and user touches the screen, initiate drag;
725                 * otherwise don't.  mScroller.isFinished should be false when
726                 * being flinged.
727                 */
728                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
729                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
730                if (finishedScrolling) {
731                    mTouchState = TOUCH_STATE_REST;
732                    mScroller.abortAnimation();
733                } else {
734                    mTouchState = TOUCH_STATE_SCROLLING;
735                }
736
737                // check if this can be the beginning of a tap on the side of the pages
738                // to scroll the current page
739                if ((mTouchState != TOUCH_STATE_PREV_PAGE) && !handlePagingClicks() &&
740                        (mTouchState != TOUCH_STATE_NEXT_PAGE)) {
741                    if (getChildCount() > 0) {
742                        int width = getMeasuredWidth();
743                        int offset = getRelativeChildOffset(mCurrentPage);
744                        if (x < offset - mPageSpacing) {
745                            mTouchState = TOUCH_STATE_PREV_PAGE;
746                        } else if (x > (width - offset + mPageSpacing)) {
747                            mTouchState = TOUCH_STATE_NEXT_PAGE;
748                        }
749                    }
750                }
751                break;
752            }
753
754            case MotionEvent.ACTION_CANCEL:
755            case MotionEvent.ACTION_UP:
756                mTouchState = TOUCH_STATE_REST;
757                mAllowLongPress = false;
758                mActivePointerId = INVALID_POINTER;
759                break;
760
761            case MotionEvent.ACTION_POINTER_UP:
762                onSecondaryPointerUp(ev);
763                break;
764        }
765
766        /*
767         * The only time we want to intercept motion events is if we are in the
768         * drag mode.
769         */
770        return mTouchState != TOUCH_STATE_REST;
771    }
772
773    protected void animateClickFeedback(View v, final Runnable r) {
774        // animate the view slightly to show click feedback running some logic after it is "pressed"
775        Animation anim = AnimationUtils.loadAnimation(getContext(),
776                R.anim.paged_view_click_feedback);
777        anim.setAnimationListener(new AnimationListener() {
778            @Override
779            public void onAnimationStart(Animation animation) {}
780            @Override
781            public void onAnimationRepeat(Animation animation) {
782                r.run();
783            }
784            @Override
785            public void onAnimationEnd(Animation animation) {}
786        });
787        v.startAnimation(anim);
788    }
789
790    /*
791     * Determines if we should change the touch state to start scrolling after the
792     * user moves their touch point too far.
793     */
794    protected void determineScrollingStart(MotionEvent ev) {
795        /*
796         * Locally do absolute value. mLastMotionX is set to the y value
797         * of the down event.
798         */
799        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
800        final float x = ev.getX(pointerIndex);
801        final float y = ev.getY(pointerIndex);
802        final int xDiff = (int) Math.abs(x - mLastMotionX);
803        final int yDiff = (int) Math.abs(y - mLastMotionY);
804
805        final int touchSlop = mTouchSlop;
806        boolean xPaged = xDiff > mPagingTouchSlop;
807        boolean xMoved = xDiff > touchSlop;
808        boolean yMoved = yDiff > touchSlop;
809
810        if (xMoved || yMoved) {
811            if (mUsePagingTouchSlop ? xPaged : xMoved) {
812                // Scroll if the user moved far enough along the X axis
813                mTouchState = TOUCH_STATE_SCROLLING;
814                mLastMotionX = x;
815                mTouchX = mScrollX;
816                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
817                pageBeginMoving();
818            }
819            // Either way, cancel any pending longpress
820            if (mAllowLongPress) {
821                mAllowLongPress = false;
822                // Try canceling the long press. It could also have been scheduled
823                // by a distant descendant, so use the mAllowLongPress flag to block
824                // everything
825                final View currentPage = getPageAt(mCurrentPage);
826                if (currentPage != null) {
827                    currentPage.cancelLongPress();
828                }
829            }
830        }
831    }
832
833    protected boolean handlePagingClicks() {
834        return false;
835    }
836
837    // This curve determines how the effect of scrolling over the limits of the page dimishes
838    // as the user pulls further and further from the bounds
839    private float overScrollInfluenceCurve(float f) {
840        f -= 1.0f;
841        return f * f * f + 1.0f;
842    }
843
844    protected void overScroll(float amount) {
845        int screenSize = getMeasuredWidth();
846
847        float f = (amount / screenSize);
848
849        if (f == 0) return;
850        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
851
852        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
853        if (amount < 0) {
854            mScrollX = overScrollAmount;
855        } else {
856            mScrollX = mMaxScrollX + overScrollAmount;
857        }
858        invalidate();
859    }
860
861    @Override
862    public boolean onTouchEvent(MotionEvent ev) {
863        // Skip touch handling if there are no pages to swipe
864        if (getChildCount() <= 0) return super.onTouchEvent(ev);
865
866        acquireVelocityTrackerAndAddMovement(ev);
867
868        final int action = ev.getAction();
869
870        switch (action & MotionEvent.ACTION_MASK) {
871        case MotionEvent.ACTION_DOWN:
872            /*
873             * If being flinged and user touches, stop the fling. isFinished
874             * will be false if being flinged.
875             */
876            if (!mScroller.isFinished()) {
877                mScroller.abortAnimation();
878            }
879
880            // Remember where the motion event started
881            mDownMotionX = mLastMotionX = ev.getX();
882            mActivePointerId = ev.getPointerId(0);
883            if (mTouchState == TOUCH_STATE_SCROLLING) {
884                pageBeginMoving();
885            }
886            break;
887
888        case MotionEvent.ACTION_MOVE:
889            if (mTouchState == TOUCH_STATE_SCROLLING) {
890                // Scroll to follow the motion event
891                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
892                final float x = ev.getX(pointerIndex);
893                final int deltaX = (int) (mLastMotionX - x);
894                mLastMotionX = x;
895
896                if (deltaX != 0) {
897                    mTouchX += deltaX;
898                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
899                    if (!mDeferScrollUpdate) {
900                        scrollBy(deltaX, 0);
901                    } else {
902                        invalidate();
903                    }
904                } else {
905                    awakenScrollBars();
906                }
907            } else {
908                determineScrollingStart(ev);
909            }
910            break;
911
912        case MotionEvent.ACTION_UP:
913            if (mTouchState == TOUCH_STATE_SCROLLING) {
914                final int activePointerId = mActivePointerId;
915                final int pointerIndex = ev.findPointerIndex(activePointerId);
916                final float x = ev.getX(pointerIndex);
917                final VelocityTracker velocityTracker = mVelocityTracker;
918                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
919                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
920                final int deltaX = (int) (x - mDownMotionX);
921                boolean isfling = Math.abs(deltaX) > MIN_LENGTH_FOR_FLING;
922                boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE;
923
924                final int snapVelocity = mSnapVelocity;
925                if ((isSignificantMove && deltaX > 0 ||
926                        (isfling && velocityX > snapVelocity)) && mCurrentPage > 0) {
927                    snapToPageWithVelocity(mCurrentPage - 1, velocityX);
928                } else if ((isSignificantMove && deltaX < 0 ||
929                        (isfling && velocityX < -snapVelocity)) &&
930                        mCurrentPage < getChildCount() - 1) {
931                    snapToPageWithVelocity(mCurrentPage + 1, velocityX);
932                } else {
933                    snapToDestination();
934                }
935            } else if (mTouchState == TOUCH_STATE_PREV_PAGE && !handlePagingClicks()) {
936                // at this point we have not moved beyond the touch slop
937                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
938                // we can just page
939                int nextPage = Math.max(0, mCurrentPage - 1);
940                if (nextPage != mCurrentPage) {
941                    snapToPage(nextPage);
942                } else {
943                    snapToDestination();
944                }
945            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE && !handlePagingClicks()) {
946                // at this point we have not moved beyond the touch slop
947                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
948                // we can just page
949                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
950                if (nextPage != mCurrentPage) {
951                    snapToPage(nextPage);
952                } else {
953                    snapToDestination();
954                }
955            }
956            mTouchState = TOUCH_STATE_REST;
957            mActivePointerId = INVALID_POINTER;
958            releaseVelocityTracker();
959            break;
960
961        case MotionEvent.ACTION_CANCEL:
962            if (mTouchState == TOUCH_STATE_SCROLLING) {
963                snapToDestination();
964            }
965            mTouchState = TOUCH_STATE_REST;
966            mActivePointerId = INVALID_POINTER;
967            releaseVelocityTracker();
968            break;
969
970        case MotionEvent.ACTION_POINTER_UP:
971            onSecondaryPointerUp(ev);
972            break;
973        }
974
975        return true;
976    }
977
978    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
979        if (mVelocityTracker == null) {
980            mVelocityTracker = VelocityTracker.obtain();
981        }
982        mVelocityTracker.addMovement(ev);
983    }
984
985    private void releaseVelocityTracker() {
986        if (mVelocityTracker != null) {
987            mVelocityTracker.recycle();
988            mVelocityTracker = null;
989        }
990    }
991
992    private void onSecondaryPointerUp(MotionEvent ev) {
993        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
994                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
995        final int pointerId = ev.getPointerId(pointerIndex);
996        if (pointerId == mActivePointerId) {
997            // This was our active pointer going up. Choose a new
998            // active pointer and adjust accordingly.
999            // TODO: Make this decision more intelligent.
1000            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1001            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1002            mLastMotionY = ev.getY(newPointerIndex);
1003            mActivePointerId = ev.getPointerId(newPointerIndex);
1004            if (mVelocityTracker != null) {
1005                mVelocityTracker.clear();
1006            }
1007        }
1008    }
1009
1010    @Override
1011    public void requestChildFocus(View child, View focused) {
1012        super.requestChildFocus(child, focused);
1013        int page = indexOfChild(child);
1014        if (page >= 0 && !isInTouchMode()) {
1015            snapToPage(page);
1016        }
1017    }
1018
1019    protected int getChildIndexForRelativeOffset(int relativeOffset) {
1020        final int childCount = getChildCount();
1021        int left;
1022        int right;
1023        for (int i = 0; i < childCount; ++i) {
1024            left = getRelativeChildOffset(i);
1025            right = (left + getChildAt(i).getMeasuredWidth());
1026            if (left <= relativeOffset && relativeOffset <= right) {
1027                return i;
1028            }
1029        }
1030        return -1;
1031    }
1032
1033    protected int getRelativeChildOffset(int index) {
1034        return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2;
1035    }
1036
1037    protected int getChildOffset(int index) {
1038        if (getChildCount() == 0)
1039            return 0;
1040
1041        int offset = getRelativeChildOffset(0);
1042        for (int i = 0; i < index; ++i) {
1043            offset += getChildAt(i).getMeasuredWidth() + mPageSpacing;
1044        }
1045        return offset;
1046    }
1047
1048    int getPageNearestToCenterOfScreen() {
1049        int minDistanceFromScreenCenter = getMeasuredWidth();
1050        int minDistanceFromScreenCenterIndex = -1;
1051        int screenCenter = mScrollX + (getMeasuredWidth() / 2);
1052        final int childCount = getChildCount();
1053        for (int i = 0; i < childCount; ++i) {
1054            View layout = (View) getChildAt(i);
1055            int childWidth = layout.getMeasuredWidth();
1056            int halfChildWidth = (childWidth / 2);
1057            int childCenter = getChildOffset(i) + halfChildWidth;
1058            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1059            if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1060                minDistanceFromScreenCenter = distanceFromScreenCenter;
1061                minDistanceFromScreenCenterIndex = i;
1062            }
1063        }
1064        return minDistanceFromScreenCenterIndex;
1065    }
1066
1067    protected void snapToDestination() {
1068        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
1069    }
1070
1071    private static class ScrollInterpolator implements Interpolator {
1072        public ScrollInterpolator() {
1073        }
1074
1075        public float getInterpolation(float t) {
1076            t -= 1.0f;
1077            return t*t*t*t*t + 1;
1078        }
1079    }
1080
1081    // We want the duration of the page snap animation to be influenced by the distance that
1082    // the screen has to travel, however, we don't want this duration to be effected in a
1083    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1084    // of travel has on the overall snap duration.
1085    float distanceInfluenceForSnapDuration(float f) {
1086        f -= 0.5f; // center the values about 0.
1087        f *= 0.3f * Math.PI / 2.0f;
1088        return (float) Math.sin(f);
1089    }
1090
1091    protected void snapToPageWithVelocity(int whichPage, int velocity) {
1092        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
1093        int halfScreenSize = getMeasuredWidth() / 2;
1094
1095        final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1096        int delta = newX - mUnboundedScrollX;
1097        int duration = 0;
1098
1099        if (Math.abs(velocity) < MIN_FLING_VELOCITY) {
1100            // If the velocity is low enough, then treat this more as an automatic page advance
1101            // as opposed to an apparent physical response to flinging
1102            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1103            return;
1104        }
1105
1106        // Here we compute a "distance" that will be used in the computation of the overall
1107        // snap duration. This is a function of the actual distance that needs to be traveled;
1108        // we keep this value close to half screen size in order to reduce the variance in snap
1109        // duration as a function of the distance the page needs to travel.
1110        float distanceRatio = 1.0f * Math.abs(delta) / 2 * halfScreenSize;
1111        float distance = halfScreenSize + halfScreenSize *
1112                distanceInfluenceForSnapDuration(distanceRatio);
1113
1114        velocity = Math.abs(velocity);
1115        velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity);
1116
1117        // we want the page's snap velocity to approximately match the velocity at which the
1118        // user flings, so we scale the duration by a value near to the derivative of the scroll
1119        // interpolator at zero, ie. 5. We use 6 to make it a little slower.
1120        duration = 6 * Math.round(1000 * Math.abs(distance / velocity));
1121
1122        snapToPage(whichPage, delta, duration);
1123    }
1124
1125    protected void snapToPage(int whichPage) {
1126        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1127    }
1128
1129    protected void snapToPage(int whichPage, int duration) {
1130        whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1131
1132        int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1133        final int sX = mUnboundedScrollX;
1134        final int delta = newX - sX;
1135        snapToPage(whichPage, delta, duration);
1136    }
1137
1138    protected void snapToPage(int whichPage, int delta, int duration) {
1139        mNextPage = whichPage;
1140
1141        View focusedChild = getFocusedChild();
1142        if (focusedChild != null && whichPage != mCurrentPage &&
1143                focusedChild == getChildAt(mCurrentPage)) {
1144            focusedChild.clearFocus();
1145        }
1146
1147        pageBeginMoving();
1148        awakenScrollBars(duration);
1149        if (duration == 0) {
1150            duration = Math.abs(delta);
1151        }
1152
1153        if (!mScroller.isFinished()) mScroller.abortAnimation();
1154        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
1155
1156        // only load some associated pages
1157        loadAssociatedPages(mNextPage);
1158        notifyPageSwitchListener();
1159        invalidate();
1160    }
1161
1162    @Override
1163    protected Parcelable onSaveInstanceState() {
1164        final SavedState state = new SavedState(super.onSaveInstanceState());
1165        state.currentPage = mCurrentPage;
1166        return state;
1167    }
1168
1169    @Override
1170    protected void onRestoreInstanceState(Parcelable state) {
1171        SavedState savedState = (SavedState) state;
1172        super.onRestoreInstanceState(savedState.getSuperState());
1173        if (savedState.currentPage != -1) {
1174            mCurrentPage = savedState.currentPage;
1175        }
1176    }
1177
1178    public void scrollLeft() {
1179        if (mScroller.isFinished()) {
1180            if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
1181        } else {
1182            if (mNextPage > 0) snapToPage(mNextPage - 1);
1183        }
1184    }
1185
1186    public void scrollRight() {
1187        if (mScroller.isFinished()) {
1188            if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
1189        } else {
1190            if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
1191        }
1192    }
1193
1194    public int getPageForView(View v) {
1195        int result = -1;
1196        if (v != null) {
1197            ViewParent vp = v.getParent();
1198            int count = getChildCount();
1199            for (int i = 0; i < count; i++) {
1200                if (vp == getChildAt(i)) {
1201                    return i;
1202                }
1203            }
1204        }
1205        return result;
1206    }
1207
1208    /**
1209     * @return True is long presses are still allowed for the current touch
1210     */
1211    public boolean allowLongPress() {
1212        return mAllowLongPress;
1213    }
1214
1215    /**
1216     * Set true to allow long-press events to be triggered, usually checked by
1217     * {@link Launcher} to accept or block dpad-initiated long-presses.
1218     */
1219    public void setAllowLongPress(boolean allowLongPress) {
1220        mAllowLongPress = allowLongPress;
1221    }
1222
1223    public static class SavedState extends BaseSavedState {
1224        int currentPage = -1;
1225
1226        SavedState(Parcelable superState) {
1227            super(superState);
1228        }
1229
1230        private SavedState(Parcel in) {
1231            super(in);
1232            currentPage = in.readInt();
1233        }
1234
1235        @Override
1236        public void writeToParcel(Parcel out, int flags) {
1237            super.writeToParcel(out, flags);
1238            out.writeInt(currentPage);
1239        }
1240
1241        public static final Parcelable.Creator<SavedState> CREATOR =
1242                new Parcelable.Creator<SavedState>() {
1243            public SavedState createFromParcel(Parcel in) {
1244                return new SavedState(in);
1245            }
1246
1247            public SavedState[] newArray(int size) {
1248                return new SavedState[size];
1249            }
1250        };
1251    }
1252
1253    public void loadAssociatedPages(int page) {
1254        if (mContentIsRefreshable) {
1255            final int count = getChildCount();
1256            if (page < count) {
1257                int lowerPageBound = getAssociatedLowerPageBound(page);
1258                int upperPageBound = getAssociatedUpperPageBound(page);
1259                for (int i = 0; i < count; ++i) {
1260                    final ViewGroup layout = (ViewGroup) getChildAt(i);
1261                    final int childCount = layout.getChildCount();
1262                    if (lowerPageBound <= i && i <= upperPageBound) {
1263                        if (mDirtyPageContent.get(i)) {
1264                            syncPageItems(i);
1265                            mDirtyPageContent.set(i, false);
1266                        }
1267                    } else {
1268                        if (childCount > 0) {
1269                            layout.removeAllViews();
1270                        }
1271                        mDirtyPageContent.set(i, true);
1272                    }
1273                }
1274            }
1275        }
1276    }
1277
1278    protected int getAssociatedLowerPageBound(int page) {
1279        return Math.max(0, page - 1);
1280    }
1281    protected int getAssociatedUpperPageBound(int page) {
1282        final int count = getChildCount();
1283        return Math.min(page + 1, count - 1);
1284    }
1285
1286    protected void startChoiceMode(int mode, ActionMode.Callback callback) {
1287        if (isChoiceMode(CHOICE_MODE_NONE)) {
1288            mChoiceMode = mode;
1289            mActionMode = startActionMode(callback);
1290        }
1291    }
1292
1293    public void endChoiceMode() {
1294        if (!isChoiceMode(CHOICE_MODE_NONE)) {
1295            mChoiceMode = CHOICE_MODE_NONE;
1296            resetCheckedGrandchildren();
1297            if (mActionMode != null) mActionMode.finish();
1298            mActionMode = null;
1299        }
1300    }
1301
1302    protected boolean isChoiceMode(int mode) {
1303        return mChoiceMode == mode;
1304    }
1305
1306    protected ArrayList<Checkable> getCheckedGrandchildren() {
1307        ArrayList<Checkable> checked = new ArrayList<Checkable>();
1308        final int childCount = getChildCount();
1309        for (int i = 0; i < childCount; ++i) {
1310            final ViewGroup layout = (ViewGroup) getChildAt(i);
1311            final int grandChildCount = layout.getChildCount();
1312            for (int j = 0; j < grandChildCount; ++j) {
1313                final View v = layout.getChildAt(j);
1314                if (v instanceof Checkable && ((Checkable) v).isChecked()) {
1315                    checked.add((Checkable) v);
1316                }
1317            }
1318        }
1319        return checked;
1320    }
1321
1322    /**
1323     * If in CHOICE_MODE_SINGLE and an item is checked, returns that item.
1324     * Otherwise, returns null.
1325     */
1326    protected Checkable getSingleCheckedGrandchild() {
1327        if (mChoiceMode == CHOICE_MODE_SINGLE) {
1328            final int childCount = getChildCount();
1329            for (int i = 0; i < childCount; ++i) {
1330                final ViewGroup layout = (ViewGroup) getChildAt(i);
1331                final int grandChildCount = layout.getChildCount();
1332                for (int j = 0; j < grandChildCount; ++j) {
1333                    final View v = layout.getChildAt(j);
1334                    if (v instanceof Checkable && ((Checkable) v).isChecked()) {
1335                        return (Checkable) v;
1336                    }
1337                }
1338            }
1339        }
1340        return null;
1341    }
1342
1343    public Object getChosenItem() {
1344        View checkedView = (View) getSingleCheckedGrandchild();
1345        if (checkedView != null) {
1346            return checkedView.getTag();
1347        }
1348        return null;
1349    }
1350
1351    protected void resetCheckedGrandchildren() {
1352        // loop through children, and set all of their children to _not_ be checked
1353        final ArrayList<Checkable> checked = getCheckedGrandchildren();
1354        for (int i = 0; i < checked.size(); ++i) {
1355            final Checkable c = checked.get(i);
1356            c.setChecked(false);
1357        }
1358    }
1359
1360    /**
1361     * This method is called ONLY to synchronize the number of pages that the paged view has.
1362     * To actually fill the pages with information, implement syncPageItems() below.  It is
1363     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
1364     * and therefore, individual page items do not need to be updated in this method.
1365     */
1366    public abstract void syncPages();
1367
1368    /**
1369     * This method is called to synchronize the items that are on a particular page.  If views on
1370     * the page can be reused, then they should be updated within this method.
1371     */
1372    public abstract void syncPageItems(int page);
1373
1374    public void invalidatePageData() {
1375        if (mContentIsRefreshable) {
1376            // Update all the pages
1377            syncPages();
1378
1379            // Mark each of the pages as dirty
1380            final int count = getChildCount();
1381            mDirtyPageContent.clear();
1382            for (int i = 0; i < count; ++i) {
1383                mDirtyPageContent.add(true);
1384            }
1385
1386            // Load any pages that are necessary for the current window of views
1387            loadAssociatedPages(mCurrentPage);
1388            mDirtyPageAlpha = true;
1389            updateAdjacentPagesAlpha();
1390            requestLayout();
1391        }
1392    }
1393}
1394