PagedView.java revision c3f9f4fcbd3fba3753335d5c9671c0893393b164
1321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung/*
2321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * Copyright (C) 2010 The Android Open Source Project
3321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *
4321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * Licensed under the Apache License, Version 2.0 (the "License");
5321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * you may not use this file except in compliance with the License.
6321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * You may obtain a copy of the License at
7321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *
8321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *      http://www.apache.org/licenses/LICENSE-2.0
9321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung *
10321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * Unless required by applicable law or agreed to in writing, software
11321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * distributed under the License is distributed on an "AS IS" BASIS,
12321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * See the License for the specific language governing permissions and
14321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * limitations under the License.
15321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung */
16321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
17321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungpackage com.android.launcher2;
18321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
19af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurkaimport com.android.launcher.R;
209f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy
21321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.content.Context;
229c4949e12c909d5e01d24386147b1c528015b31bAdam Cohenimport android.content.res.TypedArray;
23241c3b451d7841ba08247beea784953eca4e8582Winson Chungimport android.graphics.Bitmap;
24321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.graphics.Canvas;
25321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.graphics.Rect;
26321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.os.Parcel;
27321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.os.Parcelable;
28321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.util.AttributeSet;
295f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chungimport android.view.ActionMode;
30321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.MotionEvent;
31321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.VelocityTracker;
32321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.View;
33321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.ViewConfiguration;
34321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.ViewGroup;
35321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.view.ViewParent;
3680baf5a6b3c62a62265f626d43d1167783c94131Winson Chungimport android.view.animation.Animation;
37e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chungimport android.view.animation.AnimationUtils;
38e0f66b546994a9bdee452851c17a148db02ec300Adam Cohenimport android.view.animation.Interpolator;
3968d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohenimport android.view.animation.Animation.AnimationListener;
405f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chungimport android.widget.Checkable;
41321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungimport android.widget.Scroller;
42321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
43af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurkaimport java.util.ArrayList;
44af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurkaimport java.util.HashMap;
4580baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
46321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung/**
47321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung * An abstraction of the original Workspace which supports browsing through a
480142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka * sequential list of "pages"
49321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung */
50321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chungpublic abstract class PagedView extends ViewGroup {
51321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private static final String TAG = "PagedView";
520142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected static final int INVALID_PAGE = -1;
53321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
5486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    // the min drag distance for a fling to register, to prevent random page shifts
559cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung    private static final int MIN_LENGTH_FOR_FLING = 25;
569cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung    // The min drag distance to trigger a page shift (regardless of velocity)
579cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung    private static final int MIN_LENGTH_FOR_MOVE = 200;
58321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
59e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private static final int PAGE_SNAP_ANIMATION_DURATION = 550;
600142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected static final float NANOTIME_DIV = 1000000000.0f;
610142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
62e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private static final float OVERSCROLL_DAMP_FACTOR = 0.08f;
63e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private static final int MINIMUM_SNAP_VELOCITY = 2200;
64e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private static final int MIN_FLING_VELOCITY = 250;
6568d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
660142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // the velocity at which a fling gesture will cause us to snap to the next page
670142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected int mSnapVelocity = 500;
680142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
690142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected float mSmoothingTime;
700142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected float mTouchX;
71321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
720142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mFirstLayout = true;
730142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
740142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected int mCurrentPage;
750142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected int mNextPage = INVALID_PAGE;
7668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    protected int mMaxScrollX;
770142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected Scroller mScroller;
78321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private VelocityTracker mVelocityTracker;
79321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
80321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private float mDownMotionX;
817426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected float mLastMotionX;
827426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected float mLastMotionY;
83f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen    private int mLastScreenCenter = -1;
84321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
850142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_REST = 0;
860142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_SCROLLING = 1;
870142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_PREV_PAGE = 2;
880142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected final static int TOUCH_STATE_NEXT_PAGE = 3;
89e45440ef0eb9edcde30767b38099b093c6a0d6b0Adam Cohen    protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
90321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
910142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected int mTouchState = TOUCH_STATE_REST;
92321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
930142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected OnLongClickListener mLongClickListener;
94321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
957426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected boolean mAllowLongPress = true;
96321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
977426c42ce01e132781faa68941d79d23cd7fdf1eMichael Jurka    protected int mTouchSlop;
98321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private int mPagingTouchSlop;
99321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private int mMaximumVelocity;
1009c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageSpacing;
1019c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingTop;
1029c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingBottom;
1039c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingLeft;
1049c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mPageLayoutPaddingRight;
105df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung    protected int mPageLayoutWidthGap;
106df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung    protected int mPageLayoutHeightGap;
1079c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mCellCountX;
1089c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen    protected int mCellCountY;
1097da1025bd7f15b04cf55c79b73e94e5e1bc959d9Winson Chung    protected boolean mCenterPagesVertically;
11068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    protected boolean mAllowOverScroll = true;
11168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    protected int mUnboundedScrollX;
112321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
113d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    // parameter that adjusts the layout to be optimized for CellLayouts with that scale factor
114d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    protected float mLayoutScale = 1.0f;
115d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
1165f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka    protected static final int INVALID_POINTER = -1;
117321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1185f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka    protected int mActivePointerId = INVALID_POINTER;
119321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
12086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    private PageSwitchListener mPageSwitchListener;
121321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
12286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    private ArrayList<Boolean> mDirtyPageContent;
12386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    private boolean mDirtyPageAlpha;
124321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1255f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    // choice modes
1265f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    protected static final int CHOICE_MODE_NONE = 0;
1275f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    protected static final int CHOICE_MODE_SINGLE = 1;
1285f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    // Multiple selection mode is not supported by all Launcher actions atm
1295f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    protected static final int CHOICE_MODE_MULTIPLE = 2;
1309f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy
131e17e19c0bd78348b0452f5b00846b2a63a749d33Michael Jurka    protected int mChoiceMode;
1325f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    private ActionMode mActionMode;
1335f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung
134241c3b451d7841ba08247beea784953eca4e8582Winson Chung    protected PagedViewIconCache mPageViewIconCache;
135241c3b451d7841ba08247beea784953eca4e8582Winson Chung
1360142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // If true, syncPages and syncPageItems will be called to refresh pages
1370142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mContentIsRefreshable = true;
1380142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1390142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // If true, modify alpha of neighboring pages as user scrolls left/right
1400142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mFadeInAdjacentScreens = true;
1410142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1420142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
1430142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // to switch to a new page
1440142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mUsePagingTouchSlop = true;
1450142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1460142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // If true, the subclass should directly update mScrollX itself in its computeScroll method
1470142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // (SmoothPagedView does this)
1480142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean mDeferScrollUpdate = false;
1490142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1501262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    protected boolean mIsPageMoving = false;
1511262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy
152241c3b451d7841ba08247beea784953eca4e8582Winson Chung    /**
153241c3b451d7841ba08247beea784953eca4e8582Winson Chung     * Simple cache mechanism for PagedViewIcon outlines.
154241c3b451d7841ba08247beea784953eca4e8582Winson Chung     */
155241c3b451d7841ba08247beea784953eca4e8582Winson Chung    class PagedViewIconCache {
156241c3b451d7841ba08247beea784953eca4e8582Winson Chung        private final HashMap<Object, Bitmap> iconOutlineCache = new HashMap<Object, Bitmap>();
157241c3b451d7841ba08247beea784953eca4e8582Winson Chung
158241c3b451d7841ba08247beea784953eca4e8582Winson Chung        public void clear() {
159241c3b451d7841ba08247beea784953eca4e8582Winson Chung            iconOutlineCache.clear();
160241c3b451d7841ba08247beea784953eca4e8582Winson Chung        }
161241c3b451d7841ba08247beea784953eca4e8582Winson Chung        public void addOutline(Object key, Bitmap b) {
162241c3b451d7841ba08247beea784953eca4e8582Winson Chung            iconOutlineCache.put(key, b);
163241c3b451d7841ba08247beea784953eca4e8582Winson Chung        }
164241c3b451d7841ba08247beea784953eca4e8582Winson Chung        public void removeOutline(Object key) {
165241c3b451d7841ba08247beea784953eca4e8582Winson Chung            if (iconOutlineCache.containsKey(key)) {
166241c3b451d7841ba08247beea784953eca4e8582Winson Chung                iconOutlineCache.remove(key);
167241c3b451d7841ba08247beea784953eca4e8582Winson Chung            }
168241c3b451d7841ba08247beea784953eca4e8582Winson Chung        }
169241c3b451d7841ba08247beea784953eca4e8582Winson Chung        public Bitmap getOutline(Object key) {
170241c3b451d7841ba08247beea784953eca4e8582Winson Chung            return iconOutlineCache.get(key);
171241c3b451d7841ba08247beea784953eca4e8582Winson Chung        }
172241c3b451d7841ba08247beea784953eca4e8582Winson Chung    }
173241c3b451d7841ba08247beea784953eca4e8582Winson Chung
17486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    public interface PageSwitchListener {
17586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        void onPageSwitch(View newPage, int newPageIndex);
176321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
177321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
178321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public PagedView(Context context) {
179321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        this(context, null);
180321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
181321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
182321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public PagedView(Context context, AttributeSet attrs) {
183321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        this(context, attrs, 0);
184321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
185321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
186321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public PagedView(Context context, AttributeSet attrs, int defStyle) {
187321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        super(context, attrs, defStyle);
1885f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        mChoiceMode = CHOICE_MODE_NONE;
189321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1909c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        TypedArray a = context.obtainStyledAttributes(attrs,
1919c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                R.styleable.PagedView, defStyle, 0);
1929c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageSpacing = a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0);
1939c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingTop = a.getDimensionPixelSize(
1949c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                R.styleable.PagedView_pageLayoutPaddingTop, 10);
1959c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingBottom = a.getDimensionPixelSize(
1969c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                R.styleable.PagedView_pageLayoutPaddingBottom, 10);
1979c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingLeft = a.getDimensionPixelSize(
1989c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                R.styleable.PagedView_pageLayoutPaddingLeft, 10);
1999c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        mPageLayoutPaddingRight = a.getDimensionPixelSize(
2009c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                R.styleable.PagedView_pageLayoutPaddingRight, 10);
201df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung        mPageLayoutWidthGap = a.getDimensionPixelSize(
202df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung                R.styleable.PagedView_pageLayoutWidthGap, -1);
203df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung        mPageLayoutHeightGap = a.getDimensionPixelSize(
204df4b83dd9d6380ab963c62d1f9d1312efc87cb0fWinson Chung                R.styleable.PagedView_pageLayoutHeightGap, -1);
2059c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        a.recycle();
2069c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen
207321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        setHapticFeedbackEnabled(false);
2080142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        init();
209321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
210321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
211321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
212321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * Initializes various states for this workspace.
213321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
2140142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void init() {
21586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mDirtyPageContent = new ArrayList<Boolean>();
21686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mDirtyPageContent.ensureCapacity(32);
217241c3b451d7841ba08247beea784953eca4e8582Winson Chung        mPageViewIconCache = new PagedViewIconCache();
218e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        mScroller = new Scroller(getContext(), new ScrollInterpolator());
21986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mCurrentPage = 0;
2207da1025bd7f15b04cf55c79b73e94e5e1bc959d9Winson Chung        mCenterPagesVertically = true;
221321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
222321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
223321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mTouchSlop = configuration.getScaledTouchSlop();
224321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
225321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
226321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
227321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
22886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
22986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mPageSwitchListener = pageSwitchListener;
23086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mPageSwitchListener != null) {
23186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
232321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
233321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
234321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
235321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
23686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * Returns the index of the currently displayed page.
237321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     *
23886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * @return The index of the currently displayed page.
239321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
24086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    int getCurrentPage() {
24186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        return mCurrentPage;
242321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
243321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
24486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    int getPageCount() {
245321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return getChildCount();
246321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
247321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
24886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    View getPageAt(int index) {
249321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return getChildAt(index);
250321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
251321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
252321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    int getScrollWidth() {
253321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return getWidth();
254321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
255321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
256321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
257bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     * Updates the scroll of the current page immediately to its final scroll position.  We use this
258bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
259bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     * the previous tab page.
260bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung     */
261bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung    protected void updateCurrentPageScroll() {
262bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung        int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
263bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung        scrollTo(newX, 0);
264bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung        mScroller.setFinalX(newX);
265bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung    }
266bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung
267bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung    /**
26886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * Sets the current page.
269321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
27086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    void setCurrentPage(int currentPage) {
27172e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy        if (!mScroller.isFinished()) {
27272e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy            mScroller.abortAnimation();
27372e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy        }
274d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
275d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // the default
276d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        if (getChildCount() == 0) {
27772e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy            return;
27872e0d34fc5d86260a7bd173aed44acf8b66b1c1dPatrick Dubroy        }
279321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
28086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
281bbc60d8e79416e37cbede55c159bf6aaa6c171d5Winson Chung        updateCurrentPageScroll();
28280baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
283321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        invalidate();
28486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        notifyPageSwitchListener();
285321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
286321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
2870142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void notifyPageSwitchListener() {
28886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mPageSwitchListener != null) {
28986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
290321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
291321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
292321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
2931262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    private void pageBeginMoving() {
2941262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy        mIsPageMoving = true;
2951262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy        onPageBeginMoving();
2961262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    }
2971262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy
2981262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    private void pageEndMoving() {
2991262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy        onPageEndMoving();
3001262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy        mIsPageMoving = false;
3011262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    }
3021262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy
3030142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // a method that subclasses can override to add behavior
3041262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    protected void onPageBeginMoving() {
3050142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
3060142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
3070142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // a method that subclasses can override to add behavior
3081262e369484ce7f2565655ed80e6299232c70bd7Patrick Dubroy    protected void onPageEndMoving() {
3090142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
3100142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
311321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
31286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * Registers the specified listener on each page contained in this workspace.
313321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     *
314321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * @param l The listener used to respond to long clicks.
315321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
316321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
317321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void setOnLongClickListener(OnLongClickListener l) {
318321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        mLongClickListener = l;
31986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        final int count = getPageCount();
320321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < count; i++) {
32186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            getPageAt(i).setOnLongClickListener(l);
322321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
323321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
324321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
325321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
32668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    public void scrollBy(int x, int y) {
32768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        scrollTo(mUnboundedScrollX + x, mScrollY + y);
32868d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    }
32968d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
33068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    @Override
3310142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    public void scrollTo(int x, int y) {
33268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        mUnboundedScrollX = x;
33368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
33468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        if (x < 0) {
33568d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            super.scrollTo(0, y);
33668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            if (mAllowOverScroll) {
33768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                overScroll(x);
33868d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            }
33968d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        } else if (x > mMaxScrollX) {
34068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            super.scrollTo(mMaxScrollX, y);
34168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            if (mAllowOverScroll) {
34268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                overScroll(x - mMaxScrollX);
34368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            }
34468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        } else {
34568d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            super.scrollTo(x, y);
34668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        }
34768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
3480142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mTouchX = x;
3490142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
3500142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
3510142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
3520142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    // we moved this functionality to a helper function so SmoothPagedView can reuse it
3530142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected boolean computeScrollHelper() {
354321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (mScroller.computeScrollOffset()) {
3555f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            mDirtyPageAlpha = true;
356321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
3570142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            invalidate();
3580142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            return true;
35986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        } else if (mNextPage != INVALID_PAGE) {
3605f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            mDirtyPageAlpha = true;
36186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
36286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mNextPage = INVALID_PAGE;
3630142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            notifyPageSwitchListener();
36473aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            // We don't want to trigger a page end moving unless the page has settled
36573aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            // and the user has stopped scrolling
36673aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            if (mTouchState == TOUCH_STATE_REST) {
36773aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen                pageEndMoving();
36873aa9755d3db1a76e9de0f55271ef5984d78ef6fAdam Cohen            }
3690142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            return true;
370321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
3710142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        return false;
3720142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
3730142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
3740142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    @Override
3750142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    public void computeScroll() {
3760142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        computeScrollHelper();
377321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
378321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
379321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
380321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
381321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
382321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
383321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (widthMode != MeasureSpec.EXACTLY) {
384321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            throw new IllegalStateException("Workspace can only be used in EXACTLY mode.");
385321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
386321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
3876b879f0a5885274a85333531e091283405d490ccAdam Lesinski        /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
3886b879f0a5885274a85333531e091283405d490ccAdam Lesinski         * of the All apps view on XLarge displays to not take up more space then it needs. Width
3896b879f0a5885274a85333531e091283405d490ccAdam Lesinski         * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
3906b879f0a5885274a85333531e091283405d490ccAdam Lesinski         * each page to have the same width.
3916b879f0a5885274a85333531e091283405d490ccAdam Lesinski         */
392321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
3936b879f0a5885274a85333531e091283405d490ccAdam Lesinski        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
3946b879f0a5885274a85333531e091283405d490ccAdam Lesinski        int maxChildHeight = 0;
3956b879f0a5885274a85333531e091283405d490ccAdam Lesinski
3966b879f0a5885274a85333531e091283405d490ccAdam Lesinski        final int verticalPadding = mPaddingTop + mPaddingBottom;
397321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
398321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        // The children are given the same width and height as the workspace
3995f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka        // unless they were set to WRAP_CONTENT
400321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int childCount = getChildCount();
401321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < childCount; i++) {
4025f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            // disallowing padding in paged view (just pass 0)
4035f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            final View child = getChildAt(i);
4045f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
4055f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4065f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            int childWidthMode;
4075f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            if (lp.width == LayoutParams.WRAP_CONTENT) {
4085f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childWidthMode = MeasureSpec.AT_MOST;
4095f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            } else {
4105f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childWidthMode = MeasureSpec.EXACTLY;
4115f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            }
4125f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4135f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            int childHeightMode;
4145f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            if (lp.height == LayoutParams.WRAP_CONTENT) {
4155f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childHeightMode = MeasureSpec.AT_MOST;
4165f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            } else {
4175f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                childHeightMode = MeasureSpec.EXACTLY;
4185f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            }
4195f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4205f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            final int childWidthMeasureSpec =
4215f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka                MeasureSpec.makeMeasureSpec(widthSize, childWidthMode);
4225f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            final int childHeightMeasureSpec =
4236b879f0a5885274a85333531e091283405d490ccAdam Lesinski                MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
4245f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka
4255f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
4266b879f0a5885274a85333531e091283405d490ccAdam Lesinski            maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight());
4276b879f0a5885274a85333531e091283405d490ccAdam Lesinski        }
4286b879f0a5885274a85333531e091283405d490ccAdam Lesinski
4296b879f0a5885274a85333531e091283405d490ccAdam Lesinski        if (heightMode == MeasureSpec.AT_MOST) {
4306b879f0a5885274a85333531e091283405d490ccAdam Lesinski            heightSize = maxChildHeight + verticalPadding;
431321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
432faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen        if (childCount > 0) {
433faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen            mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
434faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen        } else {
435faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen            mMaxScrollX = 0;
436faa28305134b0d391d2baf1bff5bff4710fe819fAdam Cohen        }
437321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
438321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        setMeasuredDimension(widthSize, heightSize);
439cfc629446ca86366665263dba6520a7b978b7c3eMichael Jurka    }
440321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
441af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka    protected void moveToNewPageWithoutMovingCellLayouts(int newCurrentPage) {
442af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage);
443af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        int delta = newX - mScrollX;
444af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka
445af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        final int screenCount = getChildCount();
446af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        for (int i = 0; i < screenCount; i++) {
447af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka            CellLayout cl = (CellLayout) getChildAt(i);
448af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka            cl.setX(cl.getX() + delta);
449af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        }
450af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka        setCurrentPage(newCurrentPage);
451af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka    }
452af91de06b99e2d5d41ce79fefa34ce2111e51917Michael Jurka
453d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    // A layout scale of 1.0f assumes that the CellLayouts, in their unshrunken state, have a
454d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    // scale of 1.0f. A layout scale of 0.8f assumes the CellLayouts have a scale of 0.8f, and
455d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    // tightens the layout accordingly
456d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    public void setLayoutScale(float childrenScale) {
457d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        mLayoutScale = childrenScale;
458d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
459d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // Now we need to do a re-layout, but preserving absolute X and Y coordinates
460d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        int childCount = getChildCount();
461d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        float childrenX[] = new float[childCount];
462d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        float childrenY[] = new float[childCount];
463d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        for (int i = 0; i < childCount; i++) {
464d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            final View child = getChildAt(i);
465d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            childrenX[i] = child.getX();
466d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            childrenY[i] = child.getY();
467d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        }
468d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        onLayout(false, mLeft, mTop, mRight, mBottom);
469d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        for (int i = 0; i < childCount; i++) {
470d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            final View child = getChildAt(i);
471d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            child.setX(childrenX[i]);
472d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            child.setY(childrenY[i]);
473d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        }
474d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // Also, the page offset has changed  (since the pages are now smaller);
475d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        // update the page offset, but again preserving absolute X and Y coordinates
476d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        moveToNewPageWithoutMovingCellLayouts(mCurrentPage);
477d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    }
478d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
479cfc629446ca86366665263dba6520a7b978b7c3eMichael Jurka    @Override
48028750fba6a2d141eb9a1e566718c17236030b815Michael Jurka    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
4815f1c509d5ad1954a7e38e77db4d5f27c7345fd39Michael Jurka        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
482321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            setHorizontalScrollBarEnabled(false);
483cfc629446ca86366665263dba6520a7b978b7c3eMichael Jurka            int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage);
484cfc629446ca86366665263dba6520a7b978b7c3eMichael Jurka            scrollTo(newX, 0);
485cfc629446ca86366665263dba6520a7b978b7c3eMichael Jurka            mScroller.setFinalX(newX);
486321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            setHorizontalScrollBarEnabled(true);
487321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mFirstLayout = false;
488321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
489321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
4906b879f0a5885274a85333531e091283405d490ccAdam Lesinski        final int verticalPadding = mPaddingTop + mPaddingBottom;
491321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int childCount = getChildCount();
492321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int childLeft = 0;
493321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (childCount > 0) {
494e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung            childLeft = getRelativeChildOffset(0);
495321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
496321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
497321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < childCount; i++) {
498321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            final View child = getChildAt(i);
499321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (child.getVisibility() != View.GONE) {
500d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka                final int childWidth = getScaledMeasuredWidth(child);
5016b879f0a5885274a85333531e091283405d490ccAdam Lesinski                final int childHeight = child.getMeasuredHeight();
5026b879f0a5885274a85333531e091283405d490ccAdam Lesinski                int childTop = mPaddingTop;
5036b879f0a5885274a85333531e091283405d490ccAdam Lesinski                if (mCenterPagesVertically) {
5046b879f0a5885274a85333531e091283405d490ccAdam Lesinski                    childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2;
5056b879f0a5885274a85333531e091283405d490ccAdam Lesinski                }
506d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
5076b879f0a5885274a85333531e091283405d490ccAdam Lesinski                child.layout(childLeft, childTop,
508d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka                        childLeft + child.getMeasuredWidth(), childTop + childHeight);
5099c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                childLeft += childWidth + mPageSpacing;
510321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
511321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
512d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
513d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            mFirstLayout = false;
514d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        }
515321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
516321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
517e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected void updateAdjacentPagesAlpha() {
5180142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        if (mFadeInAdjacentScreens) {
5190142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) {
520e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                int halfScreenSize = getMeasuredWidth() / 2;
521e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                int screenCenter = mScrollX + halfScreenSize;
5220142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                final int childCount = getChildCount();
5230142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                for (int i = 0; i < childCount; ++i) {
5240142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    View layout = (View) getChildAt(i);
525d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka                    int childWidth = getScaledMeasuredWidth(layout);
5260142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    int halfChildWidth = (childWidth / 2);
5270142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    int childCenter = getChildOffset(i) + halfChildWidth;
528e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung
529b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                    // On the first layout, we may not have a width nor a proper offset, so for now
530b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                    // we should just assume full page width (and calculate the offset according to
531b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                    // that).
532b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                    if (childWidth <= 0) {
533b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                        childWidth = getMeasuredWidth();
534b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                        childCenter = (i * childWidth) + (childWidth / 2);
535b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                    }
536b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung
537e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                    int d = halfChildWidth;
538e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                    int distanceFromScreenCenter = childCenter - screenCenter;
539e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                    if (distanceFromScreenCenter > 0) {
540e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                        if (i > 0) {
541d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka                            d += getScaledMeasuredWidth(getChildAt(i - 1)) / 2;
542e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                        }
5430142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    } else {
544e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                        if (i < childCount - 1) {
545d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka                            d += getScaledMeasuredWidth(getChildAt(i + 1)) / 2;
546e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                        }
5470142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    }
5489c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen                    d += mPageSpacing;
549e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung
550b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                    // Preventing potential divide-by-zero
551b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung                    d = Math.max(1, d);
552b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung
553e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                    float dimAlpha = (float) (Math.abs(distanceFromScreenCenter)) / d;
554e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                    dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha)));
555e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung                    float alpha = 1.0f - dimAlpha;
556e8878e3c5ac2b426be931018493ce82bd9c90378Winson Chung
557f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen                    if (alpha < ALPHA_QUANTIZE_LEVEL) {
558f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen                        alpha = 0.0f;
559f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen                    } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) {
560f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen                        alpha = 1.0f;
561f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen                    }
562f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen
5630142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    if (Float.compare(alpha, layout.getAlpha()) != 0) {
5640142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        layout.setAlpha(alpha);
5650142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    }
566affd7b4d23cecb4ed74133dd8bd9a5ede099c562Winson Chung                }
5670142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                mDirtyPageAlpha = false;
568321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
569321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
570e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
5710142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
572f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen    protected void screenScrolled(int screenCenter) {
573f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen    }
574f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen
575e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    @Override
576e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected void dispatchDraw(Canvas canvas) {
577f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen        int halfScreenSize = getMeasuredWidth() / 2;
578f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen        int screenCenter = mScrollX + halfScreenSize;
579f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen
580f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen        if (screenCenter != mLastScreenCenter) {
581f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen            screenScrolled(screenCenter);
582f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen            updateAdjacentPagesAlpha();
583f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen            mLastScreenCenter = screenCenter;
584f34bab59fc0260f926aec44d044883dce1b4191fAdam Cohen        }
5850142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
586e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        // Find out which screens are visible; as an optimization we only call draw on them
5870142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        // As an optimization, this code assumes that all pages have the same width as the 0th
5880142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        // page.
5890142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        final int pageCount = getChildCount();
590c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka        if (pageCount > 0) {
591d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            final int pageWidth = getScaledMeasuredWidth(getChildAt(0));
592c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            final int screenWidth = getMeasuredWidth();
593c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            int x = getRelativeChildOffset(0) + pageWidth;
594c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            int leftScreen = 0;
595c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            int rightScreen = 0;
596c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            while (x <= mScrollX) {
597c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka                leftScreen++;
598c3f9f4fcbd3fba3753335d5c9671c0893393b164Winson Chung                x += getScaledMeasuredWidth(getChildAt(leftScreen)) + mPageSpacing;
599c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            }
600c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            rightScreen = leftScreen;
601c3f9f4fcbd3fba3753335d5c9671c0893393b164Winson Chung            while (x < mScrollX + screenWidth && rightScreen < pageCount) {
602c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka                rightScreen++;
603c3f9f4fcbd3fba3753335d5c9671c0893393b164Winson Chung                if (rightScreen < pageCount) {
604c3f9f4fcbd3fba3753335d5c9671c0893393b164Winson Chung                    x += getScaledMeasuredWidth(getChildAt(rightScreen)) + mPageSpacing;
605c3f9f4fcbd3fba3753335d5c9671c0893393b164Winson Chung                }
606c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            }
607c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            rightScreen = Math.min(getChildCount() - 1, rightScreen);
6080142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
609c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            final long drawingTime = getDrawingTime();
61029d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung            // Clip to the bounds
61129d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung            canvas.save();
61229d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung            canvas.clipRect(mScrollX, mScrollY, mScrollX + mRight - mLeft,
61329d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung                    mScrollY + mBottom - mTop);
61429d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung
615c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            for (int i = leftScreen; i <= rightScreen; i++) {
616c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka                drawChild(canvas, getChildAt(i), drawingTime);
617c4fb9173e73c0092a089512734c0d7df13189014Michael Jurka            }
61829d6fea296ebecb607525c8245a54696ad7c5db7Winson Chung            canvas.restore();
6190142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        }
620321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
621321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
622321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
623321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
62486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int page = indexOfChild(child);
62586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (page != mCurrentPage || !mScroller.isFinished()) {
62686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            snapToPage(page);
627321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            return true;
628321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
629321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return false;
630321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
631321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
632321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
633321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
63486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int focusablePage;
63586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mNextPage != INVALID_PAGE) {
63686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            focusablePage = mNextPage;
637321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else {
63886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            focusablePage = mCurrentPage;
639321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
64086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        View v = getPageAt(focusablePage);
641321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (v != null) {
642321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            v.requestFocus(direction, previouslyFocusedRect);
643321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
644321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return false;
645321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
646321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
647321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
648321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean dispatchUnhandledMove(View focused, int direction) {
649321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (direction == View.FOCUS_LEFT) {
65086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (getCurrentPage() > 0) {
65186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                snapToPage(getCurrentPage() - 1);
652321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return true;
653321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
654321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else if (direction == View.FOCUS_RIGHT) {
65586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (getCurrentPage() < getPageCount() - 1) {
65686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                snapToPage(getCurrentPage() + 1);
657321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return true;
658321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
659321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
660321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return super.dispatchUnhandledMove(focused, direction);
661321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
662321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
663321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
664321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
66586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
66686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            getPageAt(mCurrentPage).addFocusables(views, direction);
667321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
668321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (direction == View.FOCUS_LEFT) {
66986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage > 0) {
67086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                getPageAt(mCurrentPage - 1).addFocusables(views, direction);
671321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
672321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else if (direction == View.FOCUS_RIGHT){
67386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage < getPageCount() - 1) {
67486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                getPageAt(mCurrentPage + 1).addFocusables(views, direction);
675321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
676321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
677321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
678321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
679321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
680321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * If one of our descendant views decides that it could be focused now, only
68186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * pass that along if it's on the current page.
682321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     *
68386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * This happens when live folders requery, and if they're off page, they
68486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * end up calling requestFocus, which pulls it on page.
685321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
686321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
687321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void focusableViewAvailable(View focused) {
68886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        View current = getPageAt(mCurrentPage);
689321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        View v = focused;
690321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        while (true) {
691321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (v == current) {
692321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                super.focusableViewAvailable(focused);
693321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return;
694321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
695321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (v == this) {
696321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return;
697321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
698321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            ViewParent parent = v.getParent();
699321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (parent instanceof View) {
700321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                v = (View)v.getParent();
701321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            } else {
702321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return;
703321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
704321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
705321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
706321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
707321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
708321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * {@inheritDoc}
709321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
710321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
711321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
712321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (disallowIntercept) {
713321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // We need to make sure to cancel our long press if
714321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // a scrollable widget takes over touch events
71586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            final View currentPage = getChildAt(mCurrentPage);
71686f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            currentPage.cancelLongPress();
717321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
718321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        super.requestDisallowInterceptTouchEvent(disallowIntercept);
719321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
720321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
721321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
722321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean onInterceptTouchEvent(MotionEvent ev) {
723321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
724321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * This method JUST determines whether we want to intercept the motion.
725321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * If we return true, onTouchEvent will be called and we do the actual
726321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * scrolling there.
727321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
728321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
72945e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        // Skip touch handling if there are no pages to swipe
73045e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
73145e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung
732321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
733321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * Shortcut the most recurring case: the user is in the dragging
734321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * state and he is moving his finger.  We want to intercept this
735321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * motion.
736321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
737321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int action = ev.getAction();
738321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if ((action == MotionEvent.ACTION_MOVE) &&
739321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                (mTouchState == TOUCH_STATE_SCROLLING)) {
740321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            return true;
741321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
742321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
743321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        switch (action & MotionEvent.ACTION_MASK) {
744321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_MOVE: {
745321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                /*
746321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
747321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * whether the user has moved far enough from his original down touch.
748321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 */
7491ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                if (mActivePointerId != INVALID_POINTER) {
7501ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                    determineScrollingStart(ev);
7511ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                    break;
7521ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                }
7531ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
7541ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
7551ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // i.e. fall through to the next case (don't break)
7561ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
7571ff706b8c03063740ca74868fea46fa47d71bb27Michael Jurka                // while it's small- this was causing a crash before we checked for INVALID_POINTER)
758321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
759321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
760321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_DOWN: {
761321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float x = ev.getX();
762321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float y = ev.getY();
763321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // Remember location of down touch
764321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mDownMotionX = x;
765321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mLastMotionX = x;
766321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mLastMotionY = y;
767321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mActivePointerId = ev.getPointerId(0);
768321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mAllowLongPress = true;
769321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
770321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                /*
771321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * If being flinged and user touches the screen, initiate drag;
772321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * otherwise don't.  mScroller.isFinished should be false when
773321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 * being flinged.
774321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                 */
775fd177c1d10085e5e12ff7df27d956a378d1139b1Michael Jurka                final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
7765f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
7775f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                if (finishedScrolling) {
7785f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                    mTouchState = TOUCH_STATE_REST;
7795f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                    mScroller.abortAnimation();
7805f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                } else {
7815f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                    mTouchState = TOUCH_STATE_SCROLLING;
7825f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                }
783321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
78486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                // check if this can be the beginning of a tap on the side of the pages
785321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // to scroll the current page
78621f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen                if ((mTouchState != TOUCH_STATE_PREV_PAGE) && !handlePagingClicks() &&
787321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                        (mTouchState != TOUCH_STATE_NEXT_PAGE)) {
788321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    if (getChildCount() > 0) {
789e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                        int width = getMeasuredWidth();
790e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                        int offset = getRelativeChildOffset(mCurrentPage);
79121f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen                        if (x < offset - mPageSpacing) {
792321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                            mTouchState = TOUCH_STATE_PREV_PAGE;
79321f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen                        } else if (x > (width - offset + mPageSpacing)) {
794321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                            mTouchState = TOUCH_STATE_NEXT_PAGE;
795321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                        }
796321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    }
797321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
798321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                break;
799321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
800321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
801321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_UP:
8021d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown                onWallpaperTap(ev);
8031d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown            case MotionEvent.ACTION_CANCEL:
804321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mTouchState = TOUCH_STATE_REST;
805321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mAllowLongPress = false;
806321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mActivePointerId = INVALID_POINTER;
807321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                break;
808321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
809321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            case MotionEvent.ACTION_POINTER_UP:
810321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                onSecondaryPointerUp(ev);
811321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                break;
812321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
813321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
814321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
815321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * The only time we want to intercept motion events is if we are in the
816321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * drag mode.
817321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
818321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return mTouchState != TOUCH_STATE_REST;
819321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
820321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
82180baf5a6b3c62a62265f626d43d1167783c94131Winson Chung    protected void animateClickFeedback(View v, final Runnable r) {
82280baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        // animate the view slightly to show click feedback running some logic after it is "pressed"
82380baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        Animation anim = AnimationUtils.loadAnimation(getContext(),
82480baf5a6b3c62a62265f626d43d1167783c94131Winson Chung                R.anim.paged_view_click_feedback);
82580baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        anim.setAnimationListener(new AnimationListener() {
82680baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            @Override
82780baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            public void onAnimationStart(Animation animation) {}
82880baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            @Override
82980baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            public void onAnimationRepeat(Animation animation) {
83080baf5a6b3c62a62265f626d43d1167783c94131Winson Chung                r.run();
83180baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            }
83280baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            @Override
83380baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            public void onAnimationEnd(Animation animation) {}
83480baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        });
83580baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        v.startAnimation(anim);
83680baf5a6b3c62a62265f626d43d1167783c94131Winson Chung    }
83780baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
838321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /*
839321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * Determines if we should change the touch state to start scrolling after the
840321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * user moves their touch point too far.
841321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
8421adf5391a3a3d215b226adf3702019c22a99e3b1Michael Jurka    protected void determineScrollingStart(MotionEvent ev) {
843321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        /*
844321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * Locally do absolute value. mLastMotionX is set to the y value
845321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         * of the down event.
846321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung         */
847321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int pointerIndex = ev.findPointerIndex(mActivePointerId);
848321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final float x = ev.getX(pointerIndex);
849321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final float y = ev.getY(pointerIndex);
850321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int xDiff = (int) Math.abs(x - mLastMotionX);
851321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int yDiff = (int) Math.abs(y - mLastMotionY);
852321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
853321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int touchSlop = mTouchSlop;
854321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        boolean xPaged = xDiff > mPagingTouchSlop;
855321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        boolean xMoved = xDiff > touchSlop;
856321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        boolean yMoved = yDiff > touchSlop;
857321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
858321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (xMoved || yMoved) {
8590142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            if (mUsePagingTouchSlop ? xPaged : xMoved) {
860321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // Scroll if the user moved far enough along the X axis
861321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mTouchState = TOUCH_STATE_SCROLLING;
862321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mLastMotionX = x;
8630142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                mTouchX = mScrollX;
8640142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
8650142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                pageBeginMoving();
866321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
867321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // Either way, cancel any pending longpress
868321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (mAllowLongPress) {
869321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mAllowLongPress = false;
870321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // Try canceling the long press. It could also have been scheduled
871321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // by a distant descendant, so use the mAllowLongPress flag to block
872321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // everything
87386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                final View currentPage = getPageAt(mCurrentPage);
87410fefb18c4f227f6a08fc24966800e49ce743be8Winson Chung                if (currentPage != null) {
87510fefb18c4f227f6a08fc24966800e49ce743be8Winson Chung                    currentPage.cancelLongPress();
87610fefb18c4f227f6a08fc24966800e49ce743be8Winson Chung                }
877321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
878321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
879321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
880321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
88121f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen    protected boolean handlePagingClicks() {
88221f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen        return false;
88321f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen    }
88421f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen
885e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // This curve determines how the effect of scrolling over the limits of the page dimishes
886e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // as the user pulls further and further from the bounds
887e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private float overScrollInfluenceCurve(float f) {
888e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f -= 1.0f;
889e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        return f * f * f + 1.0f;
890e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    }
891e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
89268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    protected void overScroll(float amount) {
893e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int screenSize = getMeasuredWidth();
894e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
895e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        float f = (amount / screenSize);
896e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
897e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        if (f == 0) return;
898e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
899e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
900e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
90168d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        if (amount < 0) {
90268d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            mScrollX = overScrollAmount;
90368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        } else {
90468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen            mScrollX = mMaxScrollX + overScrollAmount;
90568d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        }
90668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        invalidate();
90768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen    }
90868d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen
909321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
910321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean onTouchEvent(MotionEvent ev) {
91145e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        // Skip touch handling if there are no pages to swipe
91245e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung        if (getChildCount() <= 0) return super.onTouchEvent(ev);
91345e1d6ec0a213a444d01466c3d4f1ded5508ed63Winson Chung
914b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        acquireVelocityTrackerAndAddMovement(ev);
915321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
916321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int action = ev.getAction();
917321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
918321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        switch (action & MotionEvent.ACTION_MASK) {
919321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_DOWN:
920321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            /*
921321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung             * If being flinged and user touches, stop the fling. isFinished
922321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung             * will be false if being flinged.
923321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung             */
924321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (!mScroller.isFinished()) {
925321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mScroller.abortAnimation();
926321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
927321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
928321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // Remember where the motion event started
929321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mDownMotionX = mLastMotionX = ev.getX();
930321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = ev.getPointerId(0);
9310142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            if (mTouchState == TOUCH_STATE_SCROLLING) {
9320142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                pageBeginMoving();
9330142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            }
934321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
935321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
936321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_MOVE:
937321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (mTouchState == TOUCH_STATE_SCROLLING) {
938321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // Scroll to follow the motion event
939321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final int pointerIndex = ev.findPointerIndex(mActivePointerId);
940321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float x = ev.getX(pointerIndex);
941321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final int deltaX = (int) (mLastMotionX - x);
942321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mLastMotionX = x;
943321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
94468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                if (deltaX != 0) {
94568d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    mTouchX += deltaX;
94668d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
94768d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    if (!mDeferScrollUpdate) {
94868d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                        scrollBy(deltaX, 0);
94968d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                    } else {
95068d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen                        invalidate();
951321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    }
952321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
953321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    awakenScrollBars();
954321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
955564976a46ef02d665aa0e455ad7867746a0b5325Adam Cohen            } else {
956321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                determineScrollingStart(ev);
957321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
958321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
959321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
960321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_UP:
961321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (mTouchState == TOUCH_STATE_SCROLLING) {
962321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final int activePointerId = mActivePointerId;
963321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final int pointerIndex = ev.findPointerIndex(activePointerId);
964321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final float x = ev.getX(pointerIndex);
965321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                final VelocityTracker velocityTracker = mVelocityTracker;
966321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
967321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
9689cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung                final int deltaX = (int) (x - mDownMotionX);
9699cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung                boolean isfling = Math.abs(deltaX) > MIN_LENGTH_FOR_FLING;
9709cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung                boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE;
971321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
9720142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                final int snapVelocity = mSnapVelocity;
9739cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung                if ((isSignificantMove && deltaX > 0 ||
974e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen                        (isfling && velocityX > snapVelocity)) && mCurrentPage > 0) {
9750142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    snapToPageWithVelocity(mCurrentPage - 1, velocityX);
9769cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung                } else if ((isSignificantMove && deltaX < 0 ||
9779cfd25f16739548111ba8fc6ba8cd83010eccef6Winson Chung                        (isfling && velocityX < -snapVelocity)) &&
97886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                        mCurrentPage < getChildCount() - 1) {
9790142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    snapToPageWithVelocity(mCurrentPage + 1, velocityX);
980321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
981321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    snapToDestination();
982321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
98321f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen            } else if (mTouchState == TOUCH_STATE_PREV_PAGE && !handlePagingClicks()) {
984321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // at this point we have not moved beyond the touch slop
985321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
986321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // we can just page
98786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                int nextPage = Math.max(0, mCurrentPage - 1);
98886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                if (nextPage != mCurrentPage) {
98986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                    snapToPage(nextPage);
990321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
991321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    snapToDestination();
992321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
99321f12b52a54d2658ed14fcf7bb1ca17a198f62beAdam Cohen            } else if (mTouchState == TOUCH_STATE_NEXT_PAGE && !handlePagingClicks()) {
994321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // at this point we have not moved beyond the touch slop
995321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
996321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                // we can just page
99786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
99886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                if (nextPage != mCurrentPage) {
99986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung                    snapToPage(nextPage);
1000321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                } else {
1001321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    snapToDestination();
1002321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
10031d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown            } else {
10041d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown                onWallpaperTap(ev);
1005321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1006321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mTouchState = TOUCH_STATE_REST;
1007321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = INVALID_POINTER;
1008b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            releaseVelocityTracker();
1009321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1010321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1011321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_CANCEL:
1012b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            if (mTouchState == TOUCH_STATE_SCROLLING) {
1013b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka                snapToDestination();
1014b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            }
1015321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mTouchState = TOUCH_STATE_REST;
1016321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = INVALID_POINTER;
1017b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            releaseVelocityTracker();
1018321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1019321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1020321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        case MotionEvent.ACTION_POINTER_UP:
1021321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            onSecondaryPointerUp(ev);
1022321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            break;
1023321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1024321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1025321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return true;
1026321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1027321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1028b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1029b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        if (mVelocityTracker == null) {
1030b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            mVelocityTracker = VelocityTracker.obtain();
1031b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        }
1032b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        mVelocityTracker.addMovement(ev);
1033b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    }
1034b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka
1035b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    private void releaseVelocityTracker() {
1036b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        if (mVelocityTracker != null) {
1037b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            mVelocityTracker.recycle();
1038b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka            mVelocityTracker = null;
1039b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka        }
1040b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka    }
1041b8f0672d7c4b9836f90c1a7517a9e47fec4c7144Michael Jurka
1042321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    private void onSecondaryPointerUp(MotionEvent ev) {
1043321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1044321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1045321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int pointerId = ev.getPointerId(pointerIndex);
1046321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (pointerId == mActivePointerId) {
1047321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // This was our active pointer going up. Choose a new
1048321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // active pointer and adjust accordingly.
1049321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            // TODO: Make this decision more intelligent.
1050321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1051321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1052321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mLastMotionY = ev.getY(newPointerIndex);
1053321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            mActivePointerId = ev.getPointerId(newPointerIndex);
1054321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (mVelocityTracker != null) {
1055321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                mVelocityTracker.clear();
1056321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1057321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
10581d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown        if (mTouchState == TOUCH_STATE_REST) {
10591d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown            onWallpaperTap(ev);
10601d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown        }
10611d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown    }
10621d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown
10631d0867c8de6a889bccde9b7e768daef182a25e5cJeff Brown    protected void onWallpaperTap(MotionEvent ev) {
1064321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1065321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1066321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
1067321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void requestChildFocus(View child, View focused) {
1068321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        super.requestChildFocus(child, focused);
106986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int page = indexOfChild(child);
107086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (page >= 0 && !isInTouchMode()) {
107186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            snapToPage(page);
1072321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1073321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1074321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1075e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected int getChildIndexForRelativeOffset(int relativeOffset) {
1076e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        final int childCount = getChildCount();
10779c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        int left;
10789c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen        int right;
1079e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        for (int i = 0; i < childCount; ++i) {
10809c4949e12c909d5e01d24386147b1c528015b31bAdam Cohen            left = getRelativeChildOffset(i);
1081d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            right = (left + getScaledMeasuredWidth(getChildAt(i)));
1082e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung            if (left <= relativeOffset && relativeOffset <= right) {
1083e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                return i;
1084e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung            }
1085e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        }
1086e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        return -1;
1087e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
1088e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung
1089321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected int getRelativeChildOffset(int index) {
1090321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2;
1091321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1092321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1093321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected int getChildOffset(int index) {
1094321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (getChildCount() == 0)
1095321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            return 0;
1096321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1097321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int offset = getRelativeChildOffset(0);
1098321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < index; ++i) {
1099d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            offset += getScaledMeasuredWidth(getChildAt(i)) + mPageSpacing;
1100321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1101321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return offset;
1102321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1103321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1104d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    protected int getScaledMeasuredWidth(View child) {
1105d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka        return (int) (child.getMeasuredWidth() * mLayoutScale + 0.5f);
1106d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka    }
1107d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka
1108d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen    int getPageNearestToCenterOfScreen() {
1109321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int minDistanceFromScreenCenter = getMeasuredWidth();
1110321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int minDistanceFromScreenCenterIndex = -1;
1111321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int screenCenter = mScrollX + (getMeasuredWidth() / 2);
1112321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int childCount = getChildCount();
1113321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        for (int i = 0; i < childCount; ++i) {
11140142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            View layout = (View) getChildAt(i);
1115d3ef3065ab0941567c45e9aec98783138b623c68Michael Jurka            int childWidth = getScaledMeasuredWidth(layout);
1116321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int halfChildWidth = (childWidth / 2);
1117321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int childCenter = getChildOffset(i) + halfChildWidth;
1118321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1119321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1120321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                minDistanceFromScreenCenter = distanceFromScreenCenter;
1121321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                minDistanceFromScreenCenterIndex = i;
1122321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1123321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1124d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen        return minDistanceFromScreenCenterIndex;
1125d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen    }
1126d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen
1127d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen    protected void snapToDestination() {
1128d19d3ca3ec22aeec48b8e555e9764b98ff8cae5fAdam Cohen        snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
1129321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1130321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1131e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    private static class ScrollInterpolator implements Interpolator {
1132e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        public ScrollInterpolator() {
1133e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        }
1134e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1135e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        public float getInterpolation(float t) {
1136e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            t -= 1.0f;
1137e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            return t*t*t*t*t + 1;
1138e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        }
1139e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    }
1140e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1141e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // We want the duration of the page snap animation to be influenced by the distance that
1142e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // the screen has to travel, however, we don't want this duration to be effected in a
1143e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1144e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    // of travel has on the overall snap duration.
1145e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    float distanceInfluenceForSnapDuration(float f) {
1146e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f -= 0.5f; // center the values about 0.
1147e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        f *= 0.3f * Math.PI / 2.0f;
1148e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        return (float) Math.sin(f);
1149e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen    }
1150e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
11510142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPageWithVelocity(int whichPage, int velocity) {
1152e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
1153e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int halfScreenSize = getMeasuredWidth() / 2;
1154e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1155e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1156e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int delta = newX - mUnboundedScrollX;
1157e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        int duration = 0;
1158e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1159e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        if (Math.abs(velocity) < MIN_FLING_VELOCITY) {
1160e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            // If the velocity is low enough, then treat this more as an automatic page advance
1161e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            // as opposed to an apparent physical response to flinging
1162e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1163e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen            return;
1164e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        }
1165e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1166e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // Here we compute a "distance" that will be used in the computation of the overall
1167e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // snap duration. This is a function of the actual distance that needs to be traveled;
1168e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // we keep this value close to half screen size in order to reduce the variance in snap
1169e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // duration as a function of the distance the page needs to travel.
1170e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        float distanceRatio = 1.0f * Math.abs(delta) / 2 * halfScreenSize;
1171e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        float distance = halfScreenSize + halfScreenSize *
1172e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen                distanceInfluenceForSnapDuration(distanceRatio);
1173e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1174e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        velocity = Math.abs(velocity);
1175e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity);
1176e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1177e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // we want the page's snap velocity to approximately match the velocity at which the
1178e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // user flings, so we scale the duration by a value near to the derivative of the scroll
1179e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        // interpolator at zero, ie. 5. We use 6 to make it a little slower.
1180e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        duration = 6 * Math.round(1000 * Math.abs(distance / velocity));
1181e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen
1182e0f66b546994a9bdee452851c17a148db02ec300Adam Cohen        snapToPage(whichPage, delta, duration);
11830142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
11840142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
11850142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPage(int whichPage) {
11865f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1187321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1188321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
11890142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPage(int whichPage, int duration) {
119086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1191321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
119286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
119368d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        final int sX = mUnboundedScrollX;
1194321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final int delta = newX - sX;
11950142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        snapToPage(whichPage, delta, duration);
11960142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
11970142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
11980142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    protected void snapToPage(int whichPage, int delta, int duration) {
11990142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mNextPage = whichPage;
12000142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
12010142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        View focusedChild = getFocusedChild();
12020142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        if (focusedChild != null && whichPage != mCurrentPage &&
12030142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                focusedChild == getChildAt(mCurrentPage)) {
12040142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            focusedChild.clearFocus();
12050142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        }
12060142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
12070142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        pageBeginMoving();
1208321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        awakenScrollBars(duration);
1209321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (duration == 0) {
1210321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            duration = Math.abs(delta);
1211321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1212321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1213321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (!mScroller.isFinished()) mScroller.abortAnimation();
121468d739365bf650fe7fecf99cd3bfe63a0d41bd12Adam Cohen        mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
121580baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
121680baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        // only load some associated pages
121786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        loadAssociatedPages(mNextPage);
12180142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        notifyPageSwitchListener();
1219321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        invalidate();
1220321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1221321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1222321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
1223321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected Parcelable onSaveInstanceState() {
1224321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        final SavedState state = new SavedState(super.onSaveInstanceState());
122586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        state.currentPage = mCurrentPage;
1226321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return state;
1227321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1228321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1229321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    @Override
1230321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    protected void onRestoreInstanceState(Parcelable state) {
1231321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        SavedState savedState = (SavedState) state;
1232321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        super.onRestoreInstanceState(savedState.getSuperState());
123386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        if (savedState.currentPage != -1) {
123486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            mCurrentPage = savedState.currentPage;
1235321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1236321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1237321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1238321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void scrollLeft() {
1239321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (mScroller.isFinished()) {
124086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
1241321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else {
124286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mNextPage > 0) snapToPage(mNextPage - 1);
1243321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1244321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1245321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1246321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void scrollRight() {
1247321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (mScroller.isFinished()) {
124886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
1249321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        } else {
125086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
1251321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1252321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1253321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
125486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    public int getPageForView(View v) {
1255321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        int result = -1;
1256321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        if (v != null) {
1257321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            ViewParent vp = v.getParent();
1258321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            int count = getChildCount();
1259321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            for (int i = 0; i < count; i++) {
1260321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                if (vp == getChildAt(i)) {
1261321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                    return i;
1262321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                }
1263321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1264321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1265321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return result;
1266321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1267321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1268321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    /**
1269321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     * @return True is long presses are still allowed for the current touch
1270321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung     */
1271321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public boolean allowLongPress() {
1272321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        return mAllowLongPress;
1273321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1274321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
12750142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    /**
12760142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka     * Set true to allow long-press events to be triggered, usually checked by
12770142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka     * {@link Launcher} to accept or block dpad-initiated long-presses.
12780142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka     */
12790142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    public void setAllowLongPress(boolean allowLongPress) {
12800142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        mAllowLongPress = allowLongPress;
12810142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka    }
12820142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka
1283321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public static class SavedState extends BaseSavedState {
128486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung        int currentPage = -1;
1285321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1286321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        SavedState(Parcelable superState) {
1287321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            super(superState);
1288321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1289321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1290321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        private SavedState(Parcel in) {
1291321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            super(in);
129286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            currentPage = in.readInt();
1293321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1294321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1295321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        @Override
1296321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        public void writeToParcel(Parcel out, int flags) {
1297321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            super.writeToParcel(out, flags);
129886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung            out.writeInt(currentPage);
1299321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        }
1300321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1301321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        public static final Parcelable.Creator<SavedState> CREATOR =
1302321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                new Parcelable.Creator<SavedState>() {
1303321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            public SavedState createFromParcel(Parcel in) {
1304321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return new SavedState(in);
1305321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1306321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
1307321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            public SavedState[] newArray(int size) {
1308321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung                return new SavedState[size];
1309321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung            }
1310321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung        };
1311321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1312321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung
131386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    public void loadAssociatedPages(int page) {
13140142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        if (mContentIsRefreshable) {
13150142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            final int count = getChildCount();
13160142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            if (page < count) {
1317e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                int lowerPageBound = getAssociatedLowerPageBound(page);
1318e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung                int upperPageBound = getAssociatedUpperPageBound(page);
13190142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                for (int i = 0; i < count; ++i) {
13200142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    final ViewGroup layout = (ViewGroup) getChildAt(i);
13210142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    final int childCount = layout.getChildCount();
13220142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    if (lowerPageBound <= i && i <= upperPageBound) {
13230142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        if (mDirtyPageContent.get(i)) {
13240142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                            syncPageItems(i);
13250142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                            mDirtyPageContent.set(i, false);
13260142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        }
13270142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                    } else {
13280142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        if (childCount > 0) {
13290142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                            layout.removeAllViews();
13300142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        }
13310142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                        mDirtyPageContent.set(i, true);
133280baf5a6b3c62a62265f626d43d1167783c94131Winson Chung                    }
133380baf5a6b3c62a62265f626d43d1167783c94131Winson Chung                }
133480baf5a6b3c62a62265f626d43d1167783c94131Winson Chung            }
133580baf5a6b3c62a62265f626d43d1167783c94131Winson Chung        }
133680baf5a6b3c62a62265f626d43d1167783c94131Winson Chung    }
133780baf5a6b3c62a62265f626d43d1167783c94131Winson Chung
1338e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected int getAssociatedLowerPageBound(int page) {
1339e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        return Math.max(0, page - 1);
1340e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
1341e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    protected int getAssociatedUpperPageBound(int page) {
1342e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        final int count = getChildCount();
1343e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung        return Math.min(page + 1, count - 1);
1344e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung    }
1345e3193b93ad7bf33e2e45319084a99b9fc986622bWinson Chung
13465f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    protected void startChoiceMode(int mode, ActionMode.Callback callback) {
1347430c53bc80be01996b45e55687885d6c05314645Patrick Dubroy        if (isChoiceMode(CHOICE_MODE_NONE)) {
1348430c53bc80be01996b45e55687885d6c05314645Patrick Dubroy            mChoiceMode = mode;
1349430c53bc80be01996b45e55687885d6c05314645Patrick Dubroy            mActionMode = startActionMode(callback);
1350430c53bc80be01996b45e55687885d6c05314645Patrick Dubroy        }
13515f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    }
13525f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung
13532b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy    public void endChoiceMode() {
13545f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        if (!isChoiceMode(CHOICE_MODE_NONE)) {
13555f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung            mChoiceMode = CHOICE_MODE_NONE;
13565f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung            resetCheckedGrandchildren();
1357e17e19c0bd78348b0452f5b00846b2a63a749d33Michael Jurka            if (mActionMode != null) mActionMode.finish();
13589f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy            mActionMode = null;
13595f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        }
13605f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    }
13615f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung
13625f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    protected boolean isChoiceMode(int mode) {
13635f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        return mChoiceMode == mode;
13645f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    }
13655f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung
13665f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    protected ArrayList<Checkable> getCheckedGrandchildren() {
13675f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        ArrayList<Checkable> checked = new ArrayList<Checkable>();
13685f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        final int childCount = getChildCount();
13695f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        for (int i = 0; i < childCount; ++i) {
1370d0d43010c7a091b6ade407d30e490527a8d16120Winson Chung            final ViewGroup layout = (ViewGroup) getChildAt(i);
13715f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung            final int grandChildCount = layout.getChildCount();
13725f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung            for (int j = 0; j < grandChildCount; ++j) {
13735f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                final View v = layout.getChildAt(j);
13749f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                if (v instanceof Checkable && ((Checkable) v).isChecked()) {
13755f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                    checked.add((Checkable) v);
13765f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung                }
13775f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung            }
13785f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        }
13795f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        return checked;
13805f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    }
13815f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung
13829f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy    /**
13839f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy     * If in CHOICE_MODE_SINGLE and an item is checked, returns that item.
13849f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy     * Otherwise, returns null.
13859f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy     */
13869f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy    protected Checkable getSingleCheckedGrandchild() {
13879f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy        if (mChoiceMode == CHOICE_MODE_SINGLE) {
13889f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy            final int childCount = getChildCount();
13899f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy            for (int i = 0; i < childCount; ++i) {
1390d0d43010c7a091b6ade407d30e490527a8d16120Winson Chung                final ViewGroup layout = (ViewGroup) getChildAt(i);
13919f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                final int grandChildCount = layout.getChildCount();
13929f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                for (int j = 0; j < grandChildCount; ++j) {
13939f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                    final View v = layout.getChildAt(j);
13949f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                    if (v instanceof Checkable && ((Checkable) v).isChecked()) {
13959f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                        return (Checkable) v;
13969f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                    }
13979f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy                }
13989f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy            }
13999f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy        }
14009f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy        return null;
14019f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy    }
14029f7aec8d4317d0407a41e12f236c962b225cb4c3Patrick Dubroy
14032b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy    public Object getChosenItem() {
14042b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy        View checkedView = (View) getSingleCheckedGrandchild();
14052b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy        if (checkedView != null) {
14062b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy            return checkedView.getTag();
14072b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy        }
14082b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy        return null;
14092b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy    }
14102b9ff37edb3f5965559b3ff7d37e418b4a2917a1Patrick Dubroy
14115f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    protected void resetCheckedGrandchildren() {
14125f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        // loop through children, and set all of their children to _not_ be checked
14135f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        final ArrayList<Checkable> checked = getCheckedGrandchildren();
14145f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        for (int i = 0; i < checked.size(); ++i) {
14155f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung            final Checkable c = checked.get(i);
14165f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung            c.setChecked(false);
14175f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung        }
14185f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung    }
14195f2aa4efeeb8b0133d891715d71553138d9f9ca7Winson Chung
142086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    /**
142186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * This method is called ONLY to synchronize the number of pages that the paged view has.
142286f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * To actually fill the pages with information, implement syncPageItems() below.  It is
142386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * guaranteed that syncPageItems() will be called for a particular page before it is shown,
142486f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * and therefore, individual page items do not need to be updated in this method.
142586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     */
1426321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public abstract void syncPages();
142786f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
142886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung    /**
142986f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * This method is called to synchronize the items that are on a particular page.  If views on
143086f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     * the page can be reused, then they should be updated within this method.
143186f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung     */
1432321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public abstract void syncPageItems(int page);
143386f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
1434321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    public void invalidatePageData() {
14350142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        if (mContentIsRefreshable) {
14360142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            // Update all the pages
14370142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            syncPages();
143886f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
14390142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            // Mark each of the pages as dirty
14400142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            final int count = getChildCount();
14410142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            mDirtyPageContent.clear();
14420142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            for (int i = 0; i < count; ++i) {
14430142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka                mDirtyPageContent.add(true);
14440142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            }
144586f7753bbd6d87ce85195b0715e5548edbfdb7d1Winson Chung
14460142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            // Load any pages that are necessary for the current window of views
14470142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            loadAssociatedPages(mCurrentPage);
14480142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            mDirtyPageAlpha = true;
1449b0b2e6f588367cf40a4270cca81af7d78f8e382eWinson Chung            updateAdjacentPagesAlpha();
14500142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka            requestLayout();
14510142d49e1378a7155bcca1fb59965d9e73016dbcMichael Jurka        }
1452321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung    }
1453321e9ee68848d9e782fd557f69cc070308ffbc9cWinson Chung}
1454