1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.LayoutTransition;
22 import android.animation.ObjectAnimator;
23 import android.animation.TimeInterpolator;
24 import android.annotation.SuppressLint;
25 import android.content.Context;
26 import android.content.res.TypedArray;
27 import android.graphics.Canvas;
28 import android.graphics.Matrix;
29 import android.graphics.Rect;
30 import android.os.Bundle;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.util.AttributeSet;
34 import android.util.DisplayMetrics;
35 import android.util.Log;
36 import android.view.InputDevice;
37 import android.view.KeyEvent;
38 import android.view.MotionEvent;
39 import android.view.VelocityTracker;
40 import android.view.View;
41 import android.view.ViewConfiguration;
42 import android.view.ViewDebug;
43 import android.view.ViewGroup;
44 import android.view.ViewParent;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.view.accessibility.AccessibilityManager;
47 import android.view.accessibility.AccessibilityNodeInfo;
48 import android.view.animation.Interpolator;
49 
50 import com.android.launcher3.anim.PropertyListBuilder;
51 import com.android.launcher3.pageindicators.PageIndicator;
52 import com.android.launcher3.util.LauncherEdgeEffect;
53 import com.android.launcher3.util.Thunk;
54 
55 import java.util.ArrayList;
56 
57 /**
58  * An abstraction of the original Workspace which supports browsing through a
59  * sequential list of "pages"
60  */
61 public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
62     private static final String TAG = "PagedView";
63     private static final boolean DEBUG = false;
64     protected static final int INVALID_PAGE = -1;
65 
66     // the min drag distance for a fling to register, to prevent random page shifts
67     private static final int MIN_LENGTH_FOR_FLING = 25;
68 
69     public static final int PAGE_SNAP_ANIMATION_DURATION = 750;
70     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
71 
72     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
73     // The page is moved more than halfway, automatically move to the next page on touch up.
74     private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
75 
76     private static final float MAX_SCROLL_PROGRESS = 1.0f;
77 
78     // The following constants need to be scaled based on density. The scaled versions will be
79     // assigned to the corresponding member variables below.
80     private static final int FLING_THRESHOLD_VELOCITY = 500;
81     private static final int MIN_SNAP_VELOCITY = 1500;
82     private static final int MIN_FLING_VELOCITY = 250;
83 
84     public static final int INVALID_RESTORE_PAGE = -1001;
85 
86     private boolean mFreeScroll = false;
87     private int mFreeScrollMinScrollX = -1;
88     private int mFreeScrollMaxScrollX = -1;
89 
90     protected int mFlingThresholdVelocity;
91     protected int mMinFlingVelocity;
92     protected int mMinSnapVelocity;
93 
94     protected boolean mFirstLayout = true;
95     private int mNormalChildHeight;
96 
97     @ViewDebug.ExportedProperty(category = "launcher")
98     protected int mCurrentPage;
99     private int mChildCountOnLastLayout;
100 
101     @ViewDebug.ExportedProperty(category = "launcher")
102     protected int mNextPage = INVALID_PAGE;
103     protected int mMaxScrollX;
104     protected LauncherScroller mScroller;
105     private Interpolator mDefaultInterpolator;
106     private VelocityTracker mVelocityTracker;
107     @Thunk int mPageSpacing = 0;
108 
109     private float mParentDownMotionX;
110     private float mParentDownMotionY;
111     private float mDownMotionX;
112     private float mDownMotionY;
113     private float mDownScrollX;
114     private float mDragViewBaselineLeft;
115     private float mLastMotionX;
116     private float mLastMotionXRemainder;
117     private float mLastMotionY;
118     private float mTotalMotionX;
119 
120     private boolean mCancelTap;
121 
122     private int[] mPageScrolls;
123 
124     protected final static int TOUCH_STATE_REST = 0;
125     protected final static int TOUCH_STATE_SCROLLING = 1;
126     protected final static int TOUCH_STATE_PREV_PAGE = 2;
127     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
128     protected final static int TOUCH_STATE_REORDERING = 4;
129 
130     protected int mTouchState = TOUCH_STATE_REST;
131 
132     protected OnLongClickListener mLongClickListener;
133 
134     protected int mTouchSlop;
135     private int mMaximumVelocity;
136     protected boolean mAllowOverScroll = true;
137     protected int[] mTempVisiblePagesRange = new int[2];
138 
139     protected static final int INVALID_POINTER = -1;
140 
141     protected int mActivePointerId = INVALID_POINTER;
142 
143     protected boolean mIsPageInTransition = false;
144 
145     protected boolean mWasInOverscroll = false;
146 
147     // Page Indicator
148     @Thunk int mPageIndicatorViewId;
149     protected PageIndicator mPageIndicator;
150     // The viewport whether the pages are to be contained (the actual view may be larger than the
151     // viewport)
152     @ViewDebug.ExportedProperty(category = "launcher")
153     private Rect mViewport = new Rect();
154 
155     // Reordering
156     // We use the min scale to determine how much to expand the actually PagedView measured
157     // dimensions such that when we are zoomed out, the view is not clipped
158     private static int REORDERING_DROP_REPOSITION_DURATION = 200;
159     @Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300;
160     private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
161 
162     private float mMinScale = 1f;
163     private boolean mUseMinScale = false;
164     @Thunk View mDragView;
165     private Runnable mSidePageHoverRunnable;
166     @Thunk int mSidePageHoverIndex = -1;
167     // This variable's scope is only for the duration of startReordering() and endReordering()
168     private boolean mReorderingStarted = false;
169     // This variable's scope is for the duration of startReordering() and after the zoomIn()
170     // animation after endReordering()
171     private boolean mIsReordering;
172     // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
173     private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
174     private int mPostReorderingPreZoomInRemainingAnimationCount;
175     private Runnable mPostReorderingPreZoomInRunnable;
176 
177     // Convenience/caching
178     private static final Matrix sTmpInvMatrix = new Matrix();
179     private static final float[] sTmpPoint = new float[2];
180     private static final int[] sTmpIntPoint = new int[2];
181     private static final Rect sTmpRect = new Rect();
182 
183     protected final Rect mInsets = new Rect();
184     protected final boolean mIsRtl;
185 
186     // Edge effect
187     private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect();
188     private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect();
189 
PagedView(Context context)190     public PagedView(Context context) {
191         this(context, null);
192     }
193 
PagedView(Context context, AttributeSet attrs)194     public PagedView(Context context, AttributeSet attrs) {
195         this(context, attrs, 0);
196     }
197 
PagedView(Context context, AttributeSet attrs, int defStyle)198     public PagedView(Context context, AttributeSet attrs, int defStyle) {
199         super(context, attrs, defStyle);
200 
201         TypedArray a = context.obtainStyledAttributes(attrs,
202                 R.styleable.PagedView, defStyle, 0);
203         mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1);
204         a.recycle();
205 
206         setHapticFeedbackEnabled(false);
207         mIsRtl = Utilities.isRtl(getResources());
208         init();
209     }
210 
211     /**
212      * Initializes various states for this workspace.
213      */
init()214     protected void init() {
215         mScroller = new LauncherScroller(getContext());
216         setDefaultInterpolator(new ScrollInterpolator());
217         mCurrentPage = 0;
218 
219         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
220         mTouchSlop = configuration.getScaledPagingTouchSlop();
221         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
222 
223         float density = getResources().getDisplayMetrics().density;
224         mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density);
225         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
226         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
227         setOnHierarchyChangeListener(this);
228         setWillNotDraw(false);
229     }
230 
setEdgeGlowColor(int color)231     protected void setEdgeGlowColor(int color) {
232         mEdgeGlowLeft.setColor(color);
233         mEdgeGlowRight.setColor(color);
234     }
235 
setDefaultInterpolator(Interpolator interpolator)236     protected void setDefaultInterpolator(Interpolator interpolator) {
237         mDefaultInterpolator = interpolator;
238         mScroller.setInterpolator(mDefaultInterpolator);
239     }
240 
initParentViews(View parent)241     public void initParentViews(View parent) {
242         if (mPageIndicatorViewId > -1) {
243             mPageIndicator = (PageIndicator) parent.findViewById(mPageIndicatorViewId);
244             mPageIndicator.setMarkersCount(getChildCount());
245             mPageIndicator.setContentDescription(getPageIndicatorDescription());
246         }
247     }
248 
249     // Convenience methods to map points from self to parent and vice versa
mapPointFromViewToParent(View v, float x, float y)250     private float[] mapPointFromViewToParent(View v, float x, float y) {
251         sTmpPoint[0] = x;
252         sTmpPoint[1] = y;
253         v.getMatrix().mapPoints(sTmpPoint);
254         sTmpPoint[0] += v.getLeft();
255         sTmpPoint[1] += v.getTop();
256         return sTmpPoint;
257     }
mapPointFromParentToView(View v, float x, float y)258     private float[] mapPointFromParentToView(View v, float x, float y) {
259         sTmpPoint[0] = x - v.getLeft();
260         sTmpPoint[1] = y - v.getTop();
261         v.getMatrix().invert(sTmpInvMatrix);
262         sTmpInvMatrix.mapPoints(sTmpPoint);
263         return sTmpPoint;
264     }
265 
updateDragViewTranslationDuringDrag()266     private void updateDragViewTranslationDuringDrag() {
267         if (mDragView != null) {
268             float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
269                     (mDragViewBaselineLeft - mDragView.getLeft());
270             float y = mLastMotionY - mDownMotionY;
271             mDragView.setTranslationX(x);
272             mDragView.setTranslationY(y);
273 
274             if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
275                     + x + ", " + y);
276         }
277     }
278 
setMinScale(float f)279     public void setMinScale(float f) {
280         mMinScale = f;
281         mUseMinScale = true;
282         requestLayout();
283     }
284 
285     @Override
setScaleX(float scaleX)286     public void setScaleX(float scaleX) {
287         super.setScaleX(scaleX);
288         if (isReordering(true)) {
289             float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
290             mLastMotionX = p[0];
291             mLastMotionY = p[1];
292             updateDragViewTranslationDuringDrag();
293         }
294     }
295 
296     // Convenience methods to get the actual width/height of the PagedView (since it is measured
297     // to be larger to account for the minimum possible scale)
getViewportWidth()298     int getViewportWidth() {
299         return mViewport.width();
300     }
getViewportHeight()301     public int getViewportHeight() {
302         return mViewport.height();
303     }
304 
305     // Convenience methods to get the offset ASSUMING that we are centering the pages in the
306     // PagedView both horizontally and vertically
getViewportOffsetX()307     int getViewportOffsetX() {
308         return (getMeasuredWidth() - getViewportWidth()) / 2;
309     }
310 
getViewportOffsetY()311     int getViewportOffsetY() {
312         return (getMeasuredHeight() - getViewportHeight()) / 2;
313     }
314 
getPageIndicator()315     public PageIndicator getPageIndicator() {
316         return mPageIndicator;
317     }
318 
319     /**
320      * Returns the index of the currently displayed page. When in free scroll mode, this is the page
321      * that the user was on before entering free scroll mode (e.g. the home screen page they
322      * long-pressed on to enter the overview). Try using {@link #getPageNearestToCenterOfScreen()}
323      * to get the page the user is currently scrolling over.
324      */
getCurrentPage()325     public int getCurrentPage() {
326         return mCurrentPage;
327     }
328 
329     /**
330      * Returns the index of page to be shown immediately afterwards.
331      */
getNextPage()332     public int getNextPage() {
333         return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
334     }
335 
getPageCount()336     public int getPageCount() {
337         return getChildCount();
338     }
339 
getPageAt(int index)340     public View getPageAt(int index) {
341         return getChildAt(index);
342     }
343 
indexToPage(int index)344     protected int indexToPage(int index) {
345         return index;
346     }
347 
348     /**
349      * Updates the scroll of the current page immediately to its final scroll position.  We use this
350      * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
351      * the previous tab page.
352      */
updateCurrentPageScroll()353     protected void updateCurrentPageScroll() {
354         // If the current page is invalid, just reset the scroll position to zero
355         int newX = 0;
356         if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
357             newX = getScrollForPage(mCurrentPage);
358         }
359         scrollTo(newX, 0);
360         mScroller.setFinalX(newX);
361         forceFinishScroller(true);
362     }
363 
abortScrollerAnimation(boolean resetNextPage)364     private void abortScrollerAnimation(boolean resetNextPage) {
365         mScroller.abortAnimation();
366         // We need to clean up the next page here to avoid computeScrollHelper from
367         // updating current page on the pass.
368         if (resetNextPage) {
369             mNextPage = INVALID_PAGE;
370         }
371     }
372 
forceFinishScroller(boolean resetNextPage)373     private void forceFinishScroller(boolean resetNextPage) {
374         mScroller.forceFinished(true);
375         // We need to clean up the next page here to avoid computeScrollHelper from
376         // updating current page on the pass.
377         if (resetNextPage) {
378             mNextPage = INVALID_PAGE;
379         }
380     }
381 
validateNewPage(int newPage)382     private int validateNewPage(int newPage) {
383         int validatedPage = newPage;
384         // When in free scroll mode, we need to clamp to the free scroll page range.
385         if (mFreeScroll) {
386             getFreeScrollPageRange(mTempVisiblePagesRange);
387             validatedPage = Math.max(mTempVisiblePagesRange[0],
388                     Math.min(newPage, mTempVisiblePagesRange[1]));
389         }
390         // Ensure that it is clamped by the actual set of children in all cases
391         validatedPage = Utilities.boundToRange(validatedPage, 0, getPageCount() - 1);
392         return validatedPage;
393     }
394 
395     /**
396      * Sets the current page.
397      */
setCurrentPage(int currentPage)398     public void setCurrentPage(int currentPage) {
399         if (!mScroller.isFinished()) {
400             abortScrollerAnimation(true);
401         }
402         // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
403         // the default
404         if (getChildCount() == 0) {
405             return;
406         }
407         mCurrentPage = validateNewPage(currentPage);
408         updateCurrentPageScroll();
409         notifyPageSwitchListener();
410         invalidate();
411     }
412 
413     /**
414      * Should be called whenever the page changes. In the case of a scroll, we wait until the page
415      * has settled.
416      */
notifyPageSwitchListener()417     protected void notifyPageSwitchListener() {
418         updatePageIndicator();
419     }
420 
updatePageIndicator()421     private void updatePageIndicator() {
422         // Update the page indicator (when we aren't reordering)
423         if (mPageIndicator != null) {
424             mPageIndicator.setContentDescription(getPageIndicatorDescription());
425             if (!isReordering(false)) {
426                 mPageIndicator.setActiveMarker(getNextPage());
427             }
428         }
429     }
pageBeginTransition()430     protected void pageBeginTransition() {
431         if (!mIsPageInTransition) {
432             mIsPageInTransition = true;
433             onPageBeginTransition();
434         }
435     }
436 
pageEndTransition()437     protected void pageEndTransition() {
438         if (mIsPageInTransition) {
439             mIsPageInTransition = false;
440             onPageEndTransition();
441         }
442     }
443 
isPageInTransition()444     protected boolean isPageInTransition() {
445         return mIsPageInTransition;
446     }
447 
448     /**
449      * Called when the page starts moving as part of the scroll. Subclasses can override this
450      * to provide custom behavior during animation.
451      */
onPageBeginTransition()452     protected void onPageBeginTransition() {
453     }
454 
455     /**
456      * Called when the page ends moving as part of the scroll. Subclasses can override this
457      * to provide custom behavior during animation.
458      */
onPageEndTransition()459     protected void onPageEndTransition() {
460         mWasInOverscroll = false;
461     }
462 
463     /**
464      * Registers the specified listener on each page contained in this workspace.
465      *
466      * @param l The listener used to respond to long clicks.
467      */
468     @Override
setOnLongClickListener(OnLongClickListener l)469     public void setOnLongClickListener(OnLongClickListener l) {
470         mLongClickListener = l;
471         final int count = getPageCount();
472         for (int i = 0; i < count; i++) {
473             getPageAt(i).setOnLongClickListener(l);
474         }
475         super.setOnLongClickListener(l);
476     }
477 
getUnboundedScrollX()478     protected int getUnboundedScrollX() {
479         return getScrollX();
480     }
481 
482     @Override
scrollBy(int x, int y)483     public void scrollBy(int x, int y) {
484         scrollTo(getUnboundedScrollX() + x, getScrollY() + y);
485     }
486 
487     @Override
scrollTo(int x, int y)488     public void scrollTo(int x, int y) {
489         // In free scroll mode, we clamp the scrollX
490         if (mFreeScroll) {
491             // If the scroller is trying to move to a location beyond the maximum allowed
492             // in the free scroll mode, we make sure to end the scroll operation.
493             if (!mScroller.isFinished() &&
494                     (x > mFreeScrollMaxScrollX || x < mFreeScrollMinScrollX)) {
495                 forceFinishScroller(false);
496             }
497 
498             x = Math.min(x, mFreeScrollMaxScrollX);
499             x = Math.max(x, mFreeScrollMinScrollX);
500         }
501 
502         boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
503         boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
504         if (isXBeforeFirstPage) {
505             super.scrollTo(mIsRtl ? mMaxScrollX : 0, y);
506             if (mAllowOverScroll) {
507                 mWasInOverscroll = true;
508                 if (mIsRtl) {
509                     overScroll(x - mMaxScrollX);
510                 } else {
511                     overScroll(x);
512                 }
513             }
514         } else if (isXAfterLastPage) {
515             super.scrollTo(mIsRtl ? 0 : mMaxScrollX, y);
516             if (mAllowOverScroll) {
517                 mWasInOverscroll = true;
518                 if (mIsRtl) {
519                     overScroll(x);
520                 } else {
521                     overScroll(x - mMaxScrollX);
522                 }
523             }
524         } else {
525             if (mWasInOverscroll) {
526                 overScroll(0);
527                 mWasInOverscroll = false;
528             }
529             super.scrollTo(x, y);
530         }
531 
532         // Update the last motion events when scrolling
533         if (isReordering(true)) {
534             float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
535             mLastMotionX = p[0];
536             mLastMotionY = p[1];
537             updateDragViewTranslationDuringDrag();
538         }
539     }
540 
sendScrollAccessibilityEvent()541     private void sendScrollAccessibilityEvent() {
542         AccessibilityManager am =
543                 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
544         if (am.isEnabled()) {
545             if (mCurrentPage != getNextPage()) {
546                 AccessibilityEvent ev =
547                         AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
548                 ev.setScrollable(true);
549                 ev.setScrollX(getScrollX());
550                 ev.setScrollY(getScrollY());
551                 ev.setMaxScrollX(mMaxScrollX);
552                 ev.setMaxScrollY(0);
553 
554                 sendAccessibilityEventUnchecked(ev);
555             }
556         }
557     }
558 
559     // we moved this functionality to a helper function so SmoothPagedView can reuse it
computeScrollHelper()560     protected boolean computeScrollHelper() {
561         return computeScrollHelper(true);
562     }
563 
computeScrollHelper(boolean shouldInvalidate)564     protected boolean computeScrollHelper(boolean shouldInvalidate) {
565         if (mScroller.computeScrollOffset()) {
566             // Don't bother scrolling if the page does not need to be moved
567             if (getUnboundedScrollX() != mScroller.getCurrX()
568                     || getScrollY() != mScroller.getCurrY()) {
569                 float scaleX = mFreeScroll ? getScaleX() : 1f;
570                 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
571                 scrollTo(scrollX, mScroller.getCurrY());
572             }
573             if (shouldInvalidate) {
574                 invalidate();
575             }
576             return true;
577         } else if (mNextPage != INVALID_PAGE && shouldInvalidate) {
578             sendScrollAccessibilityEvent();
579 
580             mCurrentPage = validateNewPage(mNextPage);
581             mNextPage = INVALID_PAGE;
582             notifyPageSwitchListener();
583 
584             // We don't want to trigger a page end moving unless the page has settled
585             // and the user has stopped scrolling
586             if (mTouchState == TOUCH_STATE_REST) {
587                 pageEndTransition();
588             }
589 
590             onPostReorderingAnimationCompleted();
591             AccessibilityManager am = (AccessibilityManager)
592                     getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
593             if (am.isEnabled()) {
594                 // Notify the user when the page changes
595                 announceForAccessibility(getCurrentPageDescription());
596             }
597             return true;
598         }
599         return false;
600     }
601 
602     @Override
computeScroll()603     public void computeScroll() {
604         computeScrollHelper();
605     }
606 
607     public static class LayoutParams extends ViewGroup.LayoutParams {
608         public boolean isFullScreenPage = false;
609 
610         // If true, the start edge of the page snaps to the start edge of the viewport.
611         public boolean matchStartEdge = false;
612 
613         /**
614          * {@inheritDoc}
615          */
LayoutParams(int width, int height)616         public LayoutParams(int width, int height) {
617             super(width, height);
618         }
619 
LayoutParams(Context context, AttributeSet attrs)620         public LayoutParams(Context context, AttributeSet attrs) {
621             super(context, attrs);
622         }
623 
LayoutParams(ViewGroup.LayoutParams source)624         public LayoutParams(ViewGroup.LayoutParams source) {
625             super(source);
626         }
627     }
628 
629     @Override
generateLayoutParams(AttributeSet attrs)630     public LayoutParams generateLayoutParams(AttributeSet attrs) {
631         return new LayoutParams(getContext(), attrs);
632     }
633 
634     @Override
generateDefaultLayoutParams()635     protected LayoutParams generateDefaultLayoutParams() {
636         return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
637     }
638 
639     @Override
generateLayoutParams(ViewGroup.LayoutParams p)640     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
641         return new LayoutParams(p);
642     }
643 
644     @Override
checkLayoutParams(ViewGroup.LayoutParams p)645     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
646         return p instanceof LayoutParams;
647     }
648 
addFullScreenPage(View page)649     public void addFullScreenPage(View page) {
650         LayoutParams lp = generateDefaultLayoutParams();
651         lp.isFullScreenPage = true;
652         super.addView(page, 0, lp);
653     }
654 
getNormalChildHeight()655     public int getNormalChildHeight() {
656         return mNormalChildHeight;
657     }
658 
659     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)660     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
661         if (getChildCount() == 0) {
662             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
663             return;
664         }
665 
666         // We measure the dimensions of the PagedView to be larger than the pages so that when we
667         // zoom out (and scale down), the view is still contained in the parent
668         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
669         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
670         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
671         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
672         // NOTE: We multiply by 2f to account for the fact that depending on the offset of the
673         // viewport, we can be at most one and a half screens offset once we scale down
674         DisplayMetrics dm = getResources().getDisplayMetrics();
675         int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
676                 dm.heightPixels + mInsets.top + mInsets.bottom);
677 
678         int parentWidthSize = (int) (2f * maxSize);
679         int parentHeightSize = (int) (2f * maxSize);
680         int scaledWidthSize, scaledHeightSize;
681         if (mUseMinScale) {
682             scaledWidthSize = (int) (parentWidthSize / mMinScale);
683             scaledHeightSize = (int) (parentHeightSize / mMinScale);
684         } else {
685             scaledWidthSize = widthSize;
686             scaledHeightSize = heightSize;
687         }
688         mViewport.set(0, 0, widthSize, heightSize);
689 
690         if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
691             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
692             return;
693         }
694 
695         // Return early if we aren't given a proper dimension
696         if (widthSize <= 0 || heightSize <= 0) {
697             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
698             return;
699         }
700 
701         /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
702          * of the All apps view on XLarge displays to not take up more space then it needs. Width
703          * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
704          * each page to have the same width.
705          */
706         final int verticalPadding = getPaddingTop() + getPaddingBottom();
707         final int horizontalPadding = getPaddingLeft() + getPaddingRight();
708 
709         int referenceChildWidth = 0;
710         // The children are given the same width and height as the workspace
711         // unless they were set to WRAP_CONTENT
712         if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
713         if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
714         if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
715         if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
716         if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
717         final int childCount = getChildCount();
718         for (int i = 0; i < childCount; i++) {
719             // disallowing padding in paged view (just pass 0)
720             final View child = getPageAt(i);
721             if (child.getVisibility() != GONE) {
722                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
723 
724                 int childWidthMode;
725                 int childHeightMode;
726                 int childWidth;
727                 int childHeight;
728 
729                 if (!lp.isFullScreenPage) {
730                     if (lp.width == LayoutParams.WRAP_CONTENT) {
731                         childWidthMode = MeasureSpec.AT_MOST;
732                     } else {
733                         childWidthMode = MeasureSpec.EXACTLY;
734                     }
735 
736                     if (lp.height == LayoutParams.WRAP_CONTENT) {
737                         childHeightMode = MeasureSpec.AT_MOST;
738                     } else {
739                         childHeightMode = MeasureSpec.EXACTLY;
740                     }
741 
742                     childWidth = getViewportWidth() - horizontalPadding
743                             - mInsets.left - mInsets.right;
744                     childHeight = getViewportHeight() - verticalPadding
745                             - mInsets.top - mInsets.bottom;
746                     mNormalChildHeight = childHeight;
747                 } else {
748                     childWidthMode = MeasureSpec.EXACTLY;
749                     childHeightMode = MeasureSpec.EXACTLY;
750 
751                     childWidth = getViewportWidth();
752                     childHeight = getViewportHeight();
753                 }
754                 if (referenceChildWidth == 0) {
755                     referenceChildWidth = childWidth;
756                 }
757 
758                 final int childWidthMeasureSpec =
759                         MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
760                     final int childHeightMeasureSpec =
761                         MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
762                 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
763             }
764         }
765         setMeasuredDimension(scaledWidthSize, scaledHeightSize);
766     }
767 
768     @SuppressLint("DrawAllocation")
769     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)770     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
771         if (getChildCount() == 0) {
772             return;
773         }
774 
775         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
776         final int childCount = getChildCount();
777 
778         int offsetX = getViewportOffsetX();
779         int offsetY = getViewportOffsetY();
780 
781         // Update the viewport offsets
782         mViewport.offset(offsetX, offsetY);
783 
784         final int startIndex = mIsRtl ? childCount - 1 : 0;
785         final int endIndex = mIsRtl ? -1 : childCount;
786         final int delta = mIsRtl ? -1 : 1;
787 
788         int verticalPadding = getPaddingTop() + getPaddingBottom();
789 
790         LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams();
791         LayoutParams nextLp;
792 
793         int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
794         if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
795             mPageScrolls = new int[childCount];
796         }
797 
798         for (int i = startIndex; i != endIndex; i += delta) {
799             final View child = getPageAt(i);
800             if (child.getVisibility() != View.GONE) {
801                 lp = (LayoutParams) child.getLayoutParams();
802                 int childTop;
803                 if (lp.isFullScreenPage) {
804                     childTop = offsetY;
805                 } else {
806                     childTop = offsetY + getPaddingTop() + mInsets.top;
807                     childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
808                 }
809 
810                 final int childWidth = child.getMeasuredWidth();
811                 final int childHeight = child.getMeasuredHeight();
812 
813                 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
814                 child.layout(childLeft, childTop,
815                         childLeft + child.getMeasuredWidth(), childTop + childHeight);
816 
817                 int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft();
818                 mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
819 
820                 int pageGap = mPageSpacing;
821                 int next = i + delta;
822                 if (next != endIndex) {
823                     nextLp = (LayoutParams) getPageAt(next).getLayoutParams();
824                 } else {
825                     nextLp = null;
826                 }
827 
828                 // Prevent full screen pages from showing in the viewport
829                 // when they are not the current page.
830                 if (lp.isFullScreenPage) {
831                     pageGap = getPaddingLeft();
832                 } else if (nextLp != null && nextLp.isFullScreenPage) {
833                     pageGap = getPaddingRight();
834                 }
835 
836                 childLeft += childWidth + pageGap + getChildGap();
837             }
838         }
839 
840         final LayoutTransition transition = getLayoutTransition();
841         // If the transition is running defer updating max scroll, as some empty pages could
842         // still be present, and a max scroll change could cause sudden jumps in scroll.
843         if (transition != null && transition.isRunning()) {
844             transition.addTransitionListener(new LayoutTransition.TransitionListener() {
845 
846                 @Override
847                 public void startTransition(LayoutTransition transition, ViewGroup container,
848                         View view, int transitionType) { }
849 
850                 @Override
851                 public void endTransition(LayoutTransition transition, ViewGroup container,
852                         View view, int transitionType) {
853                     // Wait until all transitions are complete.
854                     if (!transition.isRunning()) {
855                         transition.removeTransitionListener(this);
856                         updateMaxScrollX();
857                     }
858                 }
859             });
860         } else {
861             updateMaxScrollX();
862         }
863 
864         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < childCount) {
865             updateCurrentPageScroll();
866             mFirstLayout = false;
867         }
868 
869         if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
870             setCurrentPage(getNextPage());
871         }
872         mChildCountOnLastLayout = childCount;
873 
874         if (isReordering(true)) {
875             updateDragViewTranslationDuringDrag();
876         }
877     }
878 
getChildGap()879     protected int getChildGap() {
880         return 0;
881     }
882 
updateMaxScrollX()883     @Thunk void updateMaxScrollX() {
884         mMaxScrollX = computeMaxScrollX();
885     }
886 
computeMaxScrollX()887     protected int computeMaxScrollX() {
888         int childCount = getChildCount();
889         if (childCount > 0) {
890             final int index = mIsRtl ? 0 : childCount - 1;
891             return getScrollForPage(index);
892         } else {
893             return 0;
894         }
895     }
896 
setPageSpacing(int pageSpacing)897     public void setPageSpacing(int pageSpacing) {
898         mPageSpacing = pageSpacing;
899         requestLayout();
900     }
901 
902     @Override
onChildViewAdded(View parent, View child)903     public void onChildViewAdded(View parent, View child) {
904         // Update the page indicator, we don't update the page indicator as we
905         // add/remove pages
906         if (mPageIndicator != null && !isReordering(false)) {
907             mPageIndicator.addMarker();
908         }
909 
910         // This ensures that when children are added, they get the correct transforms / alphas
911         // in accordance with any scroll effects.
912         updateFreescrollBounds();
913         invalidate();
914     }
915 
916     @Override
onChildViewRemoved(View parent, View child)917     public void onChildViewRemoved(View parent, View child) {
918         updateFreescrollBounds();
919         invalidate();
920     }
921 
removeMarkerForView()922     private void removeMarkerForView() {
923         // Update the page indicator, we don't update the page indicator as we
924         // add/remove pages
925         if (mPageIndicator != null && !isReordering(false)) {
926             mPageIndicator.removeMarker();
927         }
928     }
929 
930     @Override
removeView(View v)931     public void removeView(View v) {
932         // XXX: We should find a better way to hook into this before the view
933         // gets removed form its parent...
934         removeMarkerForView();
935         super.removeView(v);
936     }
937     @Override
removeViewInLayout(View v)938     public void removeViewInLayout(View v) {
939         // XXX: We should find a better way to hook into this before the view
940         // gets removed form its parent...
941         removeMarkerForView();
942         super.removeViewInLayout(v);
943     }
944     @Override
removeViewAt(int index)945     public void removeViewAt(int index) {
946         // XXX: We should find a better way to hook into this before the view
947         // gets removed form its parent...
948         removeMarkerForView();
949         super.removeViewAt(index);
950     }
951     @Override
removeAllViewsInLayout()952     public void removeAllViewsInLayout() {
953         // Update the page indicator, we don't update the page indicator as we
954         // add/remove pages
955         if (mPageIndicator != null) {
956             mPageIndicator.setMarkersCount(0);
957         }
958 
959         super.removeAllViewsInLayout();
960     }
961 
getChildOffset(int index)962     protected int getChildOffset(int index) {
963         if (index < 0 || index > getChildCount() - 1) return 0;
964 
965         int offset = getPageAt(index).getLeft() - getViewportOffsetX();
966 
967         return offset;
968     }
969 
getFreeScrollPageRange(int[] range)970     protected void getFreeScrollPageRange(int[] range) {
971         range[0] = 0;
972         range[1] = Math.max(0, getChildCount() - 1);
973     }
974 
975     @Override
draw(Canvas canvas)976     public void draw(Canvas canvas) {
977         super.draw(canvas);
978         if (getPageCount() > 0) {
979             if (!mEdgeGlowLeft.isFinished()) {
980                 final int restoreCount = canvas.save();
981                 Rect display = mViewport;
982                 canvas.translate(display.left, display.top);
983                 canvas.rotate(270);
984 
985                 getEdgeVerticalPosition(sTmpIntPoint);
986                 canvas.translate(display.top - sTmpIntPoint[1], 0);
987                 mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
988                 if (mEdgeGlowLeft.draw(canvas)) {
989                     postInvalidateOnAnimation();
990                 }
991                 canvas.restoreToCount(restoreCount);
992             }
993             if (!mEdgeGlowRight.isFinished()) {
994                 final int restoreCount = canvas.save();
995                 Rect display = mViewport;
996                 canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top);
997                 canvas.rotate(90);
998 
999                 getEdgeVerticalPosition(sTmpIntPoint);
1000 
1001                 canvas.translate(sTmpIntPoint[0] - display.top, -display.width());
1002                 mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width());
1003                 if (mEdgeGlowRight.draw(canvas)) {
1004                     postInvalidateOnAnimation();
1005                 }
1006                 canvas.restoreToCount(restoreCount);
1007             }
1008         }
1009     }
1010 
1011     /**
1012      * Returns the top and bottom position for the edge effect.
1013      */
getEdgeVerticalPosition(int[] pos)1014     protected abstract void getEdgeVerticalPosition(int[] pos);
1015 
1016     @Override
requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate)1017     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
1018         int page = indexToPage(indexOfChild(child));
1019         if (page != mCurrentPage || !mScroller.isFinished()) {
1020             snapToPage(page);
1021             return true;
1022         }
1023         return false;
1024     }
1025 
1026     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)1027     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1028         int focusablePage;
1029         if (mNextPage != INVALID_PAGE) {
1030             focusablePage = mNextPage;
1031         } else {
1032             focusablePage = mCurrentPage;
1033         }
1034         View v = getPageAt(focusablePage);
1035         if (v != null) {
1036             return v.requestFocus(direction, previouslyFocusedRect);
1037         }
1038         return false;
1039     }
1040 
1041     @Override
dispatchUnhandledMove(View focused, int direction)1042     public boolean dispatchUnhandledMove(View focused, int direction) {
1043         if (super.dispatchUnhandledMove(focused, direction)) {
1044             return true;
1045         }
1046 
1047         if (mIsRtl) {
1048             if (direction == View.FOCUS_LEFT) {
1049                 direction = View.FOCUS_RIGHT;
1050             } else if (direction == View.FOCUS_RIGHT) {
1051                 direction = View.FOCUS_LEFT;
1052             }
1053         }
1054         if (direction == View.FOCUS_LEFT) {
1055             if (getCurrentPage() > 0) {
1056                 snapToPage(getCurrentPage() - 1);
1057                 return true;
1058             }
1059         } else if (direction == View.FOCUS_RIGHT) {
1060             if (getCurrentPage() < getPageCount() - 1) {
1061                 snapToPage(getCurrentPage() + 1);
1062                 return true;
1063             }
1064         }
1065         return false;
1066     }
1067 
1068     @Override
addFocusables(ArrayList<View> views, int direction, int focusableMode)1069     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1070         if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) {
1071             return;
1072         }
1073 
1074         // XXX-RTL: This will be fixed in a future CL
1075         if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
1076             getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
1077         }
1078         if (direction == View.FOCUS_LEFT) {
1079             if (mCurrentPage > 0) {
1080                 getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
1081             }
1082         } else if (direction == View.FOCUS_RIGHT){
1083             if (mCurrentPage < getPageCount() - 1) {
1084                 getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
1085             }
1086         }
1087     }
1088 
1089     /**
1090      * If one of our descendant views decides that it could be focused now, only
1091      * pass that along if it's on the current page.
1092      *
1093      * This happens when live folders requery, and if they're off page, they
1094      * end up calling requestFocus, which pulls it on page.
1095      */
1096     @Override
focusableViewAvailable(View focused)1097     public void focusableViewAvailable(View focused) {
1098         View current = getPageAt(mCurrentPage);
1099         View v = focused;
1100         while (true) {
1101             if (v == current) {
1102                 super.focusableViewAvailable(focused);
1103                 return;
1104             }
1105             if (v == this) {
1106                 return;
1107             }
1108             ViewParent parent = v.getParent();
1109             if (parent instanceof View) {
1110                 v = (View)v.getParent();
1111             } else {
1112                 return;
1113             }
1114         }
1115     }
1116 
1117     /**
1118      * {@inheritDoc}
1119      */
1120     @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)1121     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1122         if (disallowIntercept) {
1123             // We need to make sure to cancel our long press if
1124             // a scrollable widget takes over touch events
1125             final View currentPage = getPageAt(mCurrentPage);
1126             currentPage.cancelLongPress();
1127         }
1128         super.requestDisallowInterceptTouchEvent(disallowIntercept);
1129     }
1130 
1131     /**
1132      * Return true if a tap at (x, y) should trigger a flip to the previous page.
1133      */
hitsPreviousPage(float x, float y)1134     protected boolean hitsPreviousPage(float x, float y) {
1135         if (mIsRtl) {
1136             return (x > (getViewportOffsetX() + getViewportWidth() -
1137                     getPaddingRight() - mPageSpacing));
1138         }
1139         return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
1140     }
1141 
1142     /**
1143      * Return true if a tap at (x, y) should trigger a flip to the next page.
1144      */
hitsNextPage(float x, float y)1145     protected boolean hitsNextPage(float x, float y) {
1146         if (mIsRtl) {
1147             return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
1148         }
1149         return  (x > (getViewportOffsetX() + getViewportWidth() -
1150                 getPaddingRight() - mPageSpacing));
1151     }
1152 
1153     /** Returns whether x and y originated within the buffered viewport */
isTouchPointInViewportWithBuffer(int x, int y)1154     private boolean isTouchPointInViewportWithBuffer(int x, int y) {
1155         sTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
1156                 mViewport.right + mViewport.width() / 2, mViewport.bottom);
1157         return sTmpRect.contains(x, y);
1158     }
1159 
1160     @Override
onInterceptTouchEvent(MotionEvent ev)1161     public boolean onInterceptTouchEvent(MotionEvent ev) {
1162         /*
1163          * This method JUST determines whether we want to intercept the motion.
1164          * If we return true, onTouchEvent will be called and we do the actual
1165          * scrolling there.
1166          */
1167         acquireVelocityTrackerAndAddMovement(ev);
1168 
1169         // Skip touch handling if there are no pages to swipe
1170         if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
1171 
1172         /*
1173          * Shortcut the most recurring case: the user is in the dragging
1174          * state and he is moving his finger.  We want to intercept this
1175          * motion.
1176          */
1177         final int action = ev.getAction();
1178         if ((action == MotionEvent.ACTION_MOVE) &&
1179                 (mTouchState == TOUCH_STATE_SCROLLING)) {
1180             return true;
1181         }
1182 
1183         switch (action & MotionEvent.ACTION_MASK) {
1184             case MotionEvent.ACTION_MOVE: {
1185                 /*
1186                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1187                  * whether the user has moved far enough from his original down touch.
1188                  */
1189                 if (mActivePointerId != INVALID_POINTER) {
1190                     determineScrollingStart(ev);
1191                 }
1192                 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
1193                 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
1194                 // i.e. fall through to the next case (don't break)
1195                 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
1196                 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
1197                 break;
1198             }
1199 
1200             case MotionEvent.ACTION_DOWN: {
1201                 final float x = ev.getX();
1202                 final float y = ev.getY();
1203                 // Remember location of down touch
1204                 mDownMotionX = x;
1205                 mDownMotionY = y;
1206                 mDownScrollX = getScrollX();
1207                 mLastMotionX = x;
1208                 mLastMotionY = y;
1209                 float[] p = mapPointFromViewToParent(this, x, y);
1210                 mParentDownMotionX = p[0];
1211                 mParentDownMotionY = p[1];
1212                 mLastMotionXRemainder = 0;
1213                 mTotalMotionX = 0;
1214                 mActivePointerId = ev.getPointerId(0);
1215 
1216                 /*
1217                  * If being flinged and user touches the screen, initiate drag;
1218                  * otherwise don't.  mScroller.isFinished should be false when
1219                  * being flinged.
1220                  */
1221                 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
1222                 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
1223 
1224                 if (finishedScrolling) {
1225                     mTouchState = TOUCH_STATE_REST;
1226                     if (!mScroller.isFinished() && !mFreeScroll) {
1227                         setCurrentPage(getNextPage());
1228                         pageEndTransition();
1229                     }
1230                 } else {
1231                     if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
1232                         mTouchState = TOUCH_STATE_SCROLLING;
1233                     } else {
1234                         mTouchState = TOUCH_STATE_REST;
1235                     }
1236                 }
1237 
1238                 break;
1239             }
1240 
1241             case MotionEvent.ACTION_UP:
1242             case MotionEvent.ACTION_CANCEL:
1243                 resetTouchState();
1244                 break;
1245 
1246             case MotionEvent.ACTION_POINTER_UP:
1247                 onSecondaryPointerUp(ev);
1248                 releaseVelocityTracker();
1249                 break;
1250         }
1251 
1252         /*
1253          * The only time we want to intercept motion events is if we are in the
1254          * drag mode.
1255          */
1256         return mTouchState != TOUCH_STATE_REST;
1257     }
1258 
determineScrollingStart(MotionEvent ev)1259     protected void determineScrollingStart(MotionEvent ev) {
1260         determineScrollingStart(ev, 1.0f);
1261     }
1262 
1263     /*
1264      * Determines if we should change the touch state to start scrolling after the
1265      * user moves their touch point too far.
1266      */
determineScrollingStart(MotionEvent ev, float touchSlopScale)1267     protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1268         // Disallow scrolling if we don't have a valid pointer index
1269         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1270         if (pointerIndex == -1) return;
1271 
1272         // Disallow scrolling if we started the gesture from outside the viewport
1273         final float x = ev.getX(pointerIndex);
1274         final float y = ev.getY(pointerIndex);
1275         if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
1276 
1277         final int xDiff = (int) Math.abs(x - mLastMotionX);
1278 
1279         final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
1280         boolean xMoved = xDiff > touchSlop;
1281 
1282         if (xMoved) {
1283             // Scroll if the user moved far enough along the X axis
1284             mTouchState = TOUCH_STATE_SCROLLING;
1285             mTotalMotionX += Math.abs(mLastMotionX - x);
1286             mLastMotionX = x;
1287             mLastMotionXRemainder = 0;
1288             onScrollInteractionBegin();
1289             pageBeginTransition();
1290             // Stop listening for things like pinches.
1291             requestDisallowInterceptTouchEvent(true);
1292         }
1293     }
1294 
cancelCurrentPageLongPress()1295     protected void cancelCurrentPageLongPress() {
1296         // Try canceling the long press. It could also have been scheduled
1297         // by a distant descendant, so use the mAllowLongPress flag to block
1298         // everything
1299         final View currentPage = getPageAt(mCurrentPage);
1300         if (currentPage != null) {
1301             currentPage.cancelLongPress();
1302         }
1303     }
1304 
getScrollProgress(int screenCenter, View v, int page)1305     protected float getScrollProgress(int screenCenter, View v, int page) {
1306         final int halfScreenSize = getViewportWidth() / 2;
1307 
1308         int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
1309         int count = getChildCount();
1310 
1311         final int totalDistance;
1312 
1313         int adjacentPage = page + 1;
1314         if ((delta < 0 && !mIsRtl) || (delta > 0 && mIsRtl)) {
1315             adjacentPage = page - 1;
1316         }
1317 
1318         if (adjacentPage < 0 || adjacentPage > count - 1) {
1319             totalDistance = v.getMeasuredWidth() + mPageSpacing;
1320         } else {
1321             totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page));
1322         }
1323 
1324         float scrollProgress = delta / (totalDistance * 1.0f);
1325         scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS);
1326         scrollProgress = Math.max(scrollProgress, - MAX_SCROLL_PROGRESS);
1327         return scrollProgress;
1328     }
1329 
getScrollForPage(int index)1330     public int getScrollForPage(int index) {
1331         if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
1332             return 0;
1333         } else {
1334             return mPageScrolls[index];
1335         }
1336     }
1337 
1338     // While layout transitions are occurring, a child's position may stray from its baseline
1339     // position. This method returns the magnitude of this stray at any given time.
getLayoutTransitionOffsetForPage(int index)1340     public int getLayoutTransitionOffsetForPage(int index) {
1341         if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
1342             return 0;
1343         } else {
1344             View child = getChildAt(index);
1345 
1346             int scrollOffset = 0;
1347             LayoutParams lp = (LayoutParams) child.getLayoutParams();
1348             if (!lp.isFullScreenPage) {
1349                 scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
1350             }
1351 
1352             int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
1353             return (int) (child.getX() - baselineX);
1354         }
1355     }
1356 
dampedOverScroll(float amount)1357     protected void dampedOverScroll(float amount) {
1358         int screenSize = getViewportWidth();
1359         float f = (amount / screenSize);
1360         if (f < 0) {
1361             mEdgeGlowLeft.onPull(-f);
1362         } else if (f > 0) {
1363             mEdgeGlowRight.onPull(f);
1364         } else {
1365             return;
1366         }
1367         invalidate();
1368     }
1369 
overScroll(float amount)1370     protected void overScroll(float amount) {
1371         dampedOverScroll(amount);
1372     }
1373 
1374     /**
1375      * return true if freescroll has been enabled, false otherwise
1376      */
enableFreeScroll()1377     public boolean enableFreeScroll() {
1378         setEnableFreeScroll(true);
1379         return true;
1380     }
1381 
disableFreeScroll()1382     public void disableFreeScroll() {
1383         setEnableFreeScroll(false);
1384     }
1385 
updateFreescrollBounds()1386     void updateFreescrollBounds() {
1387         getFreeScrollPageRange(mTempVisiblePagesRange);
1388         if (mIsRtl) {
1389             mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
1390             mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
1391         } else {
1392             mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
1393             mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
1394         }
1395     }
1396 
setEnableFreeScroll(boolean freeScroll)1397     private void setEnableFreeScroll(boolean freeScroll) {
1398         boolean wasFreeScroll = mFreeScroll;
1399         mFreeScroll = freeScroll;
1400 
1401         if (mFreeScroll) {
1402             updateFreescrollBounds();
1403             getFreeScrollPageRange(mTempVisiblePagesRange);
1404             if (getCurrentPage() < mTempVisiblePagesRange[0]) {
1405                 setCurrentPage(mTempVisiblePagesRange[0]);
1406             } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
1407                 setCurrentPage(mTempVisiblePagesRange[1]);
1408             }
1409         } else if (wasFreeScroll) {
1410             snapToPage(getNextPage());
1411         }
1412 
1413         setEnableOverscroll(!freeScroll);
1414     }
1415 
setEnableOverscroll(boolean enable)1416     protected void setEnableOverscroll(boolean enable) {
1417         mAllowOverScroll = enable;
1418     }
1419 
getNearestHoverOverPageIndex()1420     private int getNearestHoverOverPageIndex() {
1421         if (mDragView != null) {
1422             int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
1423                     + mDragView.getTranslationX());
1424             getFreeScrollPageRange(mTempVisiblePagesRange);
1425             int minDistance = Integer.MAX_VALUE;
1426             int minIndex = indexOfChild(mDragView);
1427             for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
1428                 View page = getPageAt(i);
1429                 int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
1430                 int d = Math.abs(dragX - pageX);
1431                 if (d < minDistance) {
1432                     minIndex = i;
1433                     minDistance = d;
1434                 }
1435             }
1436             return minIndex;
1437         }
1438         return -1;
1439     }
1440 
1441     @Override
onTouchEvent(MotionEvent ev)1442     public boolean onTouchEvent(MotionEvent ev) {
1443         super.onTouchEvent(ev);
1444 
1445         // Skip touch handling if there are no pages to swipe
1446         if (getChildCount() <= 0) return super.onTouchEvent(ev);
1447 
1448         acquireVelocityTrackerAndAddMovement(ev);
1449 
1450         final int action = ev.getAction();
1451 
1452         switch (action & MotionEvent.ACTION_MASK) {
1453         case MotionEvent.ACTION_DOWN:
1454             /*
1455              * If being flinged and user touches, stop the fling. isFinished
1456              * will be false if being flinged.
1457              */
1458             if (!mScroller.isFinished()) {
1459                 abortScrollerAnimation(false);
1460             }
1461 
1462             // Remember where the motion event started
1463             mDownMotionX = mLastMotionX = ev.getX();
1464             mDownMotionY = mLastMotionY = ev.getY();
1465             mDownScrollX = getScrollX();
1466             float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1467             mParentDownMotionX = p[0];
1468             mParentDownMotionY = p[1];
1469             mLastMotionXRemainder = 0;
1470             mTotalMotionX = 0;
1471             mActivePointerId = ev.getPointerId(0);
1472 
1473             if (mTouchState == TOUCH_STATE_SCROLLING) {
1474                 onScrollInteractionBegin();
1475                 pageBeginTransition();
1476             }
1477             break;
1478 
1479         case MotionEvent.ACTION_MOVE:
1480             if (mTouchState == TOUCH_STATE_SCROLLING) {
1481                 // Scroll to follow the motion event
1482                 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1483 
1484                 if (pointerIndex == -1) return true;
1485 
1486                 final float x = ev.getX(pointerIndex);
1487                 final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1488 
1489                 mTotalMotionX += Math.abs(deltaX);
1490 
1491                 // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
1492                 // keep the remainder because we are actually testing if we've moved from the last
1493                 // scrolled position (which is discrete).
1494                 if (Math.abs(deltaX) >= 1.0f) {
1495                     scrollBy((int) deltaX, 0);
1496                     mLastMotionX = x;
1497                     mLastMotionXRemainder = deltaX - (int) deltaX;
1498                 } else {
1499                     awakenScrollBars();
1500                 }
1501             } else if (mTouchState == TOUCH_STATE_REORDERING) {
1502                 // Update the last motion position
1503                 mLastMotionX = ev.getX();
1504                 mLastMotionY = ev.getY();
1505 
1506                 // Update the parent down so that our zoom animations take this new movement into
1507                 // account
1508                 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1509                 mParentDownMotionX = pt[0];
1510                 mParentDownMotionY = pt[1];
1511                 updateDragViewTranslationDuringDrag();
1512 
1513                 // Find the closest page to the touch point
1514                 final int dragViewIndex = indexOfChild(mDragView);
1515 
1516                 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
1517                 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
1518                 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
1519                 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
1520 
1521                 final int pageUnderPointIndex = getNearestHoverOverPageIndex();
1522                 // Do not allow any page to be moved to 0th position.
1523                 if (pageUnderPointIndex > 0 && pageUnderPointIndex != indexOfChild(mDragView)) {
1524                     mTempVisiblePagesRange[0] = 0;
1525                     mTempVisiblePagesRange[1] = getPageCount() - 1;
1526                     getFreeScrollPageRange(mTempVisiblePagesRange);
1527                     if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
1528                             pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
1529                             pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
1530                         mSidePageHoverIndex = pageUnderPointIndex;
1531                         mSidePageHoverRunnable = new Runnable() {
1532                             @Override
1533                             public void run() {
1534                                 // Setup the scroll to the correct page before we swap the views
1535                                 snapToPage(pageUnderPointIndex);
1536 
1537                                 // For each of the pages between the paged view and the drag view,
1538                                 // animate them from the previous position to the new position in
1539                                 // the layout (as a result of the drag view moving in the layout)
1540                                 int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
1541                                 int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
1542                                         dragViewIndex + 1 : pageUnderPointIndex;
1543                                 int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
1544                                         dragViewIndex - 1 : pageUnderPointIndex;
1545                                 for (int i = lowerIndex; i <= upperIndex; ++i) {
1546                                     View v = getChildAt(i);
1547                                     // dragViewIndex < pageUnderPointIndex, so after we remove the
1548                                     // drag view all subsequent views to pageUnderPointIndex will
1549                                     // shift down.
1550                                     int oldX = getViewportOffsetX() + getChildOffset(i);
1551                                     int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
1552 
1553                                     // Animate the view translation from its old position to its new
1554                                     // position
1555                                     ObjectAnimator anim = (ObjectAnimator) v.getTag();
1556                                     if (anim != null) {
1557                                         anim.cancel();
1558                                     }
1559 
1560                                     v.setTranslationX(oldX - newX);
1561                                     anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0);
1562                                     anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
1563                                     anim.start();
1564                                     v.setTag(anim);
1565                                 }
1566 
1567                                 removeView(mDragView);
1568                                 addView(mDragView, pageUnderPointIndex);
1569                                 mSidePageHoverIndex = -1;
1570                                 if (mPageIndicator != null) {
1571                                     mPageIndicator.setActiveMarker(getNextPage());
1572                                 }
1573                             }
1574                         };
1575                         postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
1576                     }
1577                 } else {
1578                     removeCallbacks(mSidePageHoverRunnable);
1579                     mSidePageHoverIndex = -1;
1580                 }
1581             } else {
1582                 determineScrollingStart(ev);
1583             }
1584             break;
1585 
1586         case MotionEvent.ACTION_UP:
1587             if (mTouchState == TOUCH_STATE_SCROLLING) {
1588                 final int activePointerId = mActivePointerId;
1589                 final int pointerIndex = ev.findPointerIndex(activePointerId);
1590                 final float x = ev.getX(pointerIndex);
1591                 final VelocityTracker velocityTracker = mVelocityTracker;
1592                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1593                 int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1594                 final int deltaX = (int) (x - mDownMotionX);
1595                 final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
1596                 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1597                         SIGNIFICANT_MOVE_THRESHOLD;
1598 
1599                 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1600 
1601                 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1602                         Math.abs(velocityX) > mFlingThresholdVelocity;
1603 
1604                 if (!mFreeScroll) {
1605                     // In the case that the page is moved far to one direction and then is flung
1606                     // in the opposite direction, we use a threshold to determine whether we should
1607                     // just return to the starting page, or if we should skip one further.
1608                     boolean returnToOriginalPage = false;
1609                     if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1610                             Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1611                         returnToOriginalPage = true;
1612                     }
1613 
1614                     int finalPage;
1615                     // We give flings precedence over large moves, which is why we short-circuit our
1616                     // test for a large move if a fling has been registered. That is, a large
1617                     // move to the left and fling to the right will register as a fling to the right.
1618                     boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
1619                     boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
1620                     if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
1621                             (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
1622                         finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
1623                         snapToPageWithVelocity(finalPage, velocityX);
1624                     } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
1625                             (isFling && isVelocityXLeft)) &&
1626                             mCurrentPage < getChildCount() - 1) {
1627                         finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
1628                         snapToPageWithVelocity(finalPage, velocityX);
1629                     } else {
1630                         snapToDestination();
1631                     }
1632                 } else {
1633                     if (!mScroller.isFinished()) {
1634                         abortScrollerAnimation(true);
1635                     }
1636 
1637                     float scaleX = getScaleX();
1638                     int vX = (int) (-velocityX * scaleX);
1639                     int initialScrollX = (int) (getScrollX() * scaleX);
1640 
1641                     mScroller.setInterpolator(mDefaultInterpolator);
1642                     mScroller.fling(initialScrollX,
1643                             getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
1644                     mNextPage = getPageNearestToCenterOfScreen((int) (mScroller.getFinalX() / scaleX));
1645                     invalidate();
1646                 }
1647                 onScrollInteractionEnd();
1648             } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1649                 // at this point we have not moved beyond the touch slop
1650                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1651                 // we can just page
1652                 int nextPage = Math.max(0, mCurrentPage - 1);
1653                 if (nextPage != mCurrentPage) {
1654                     snapToPage(nextPage);
1655                 } else {
1656                     snapToDestination();
1657                 }
1658             } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1659                 // at this point we have not moved beyond the touch slop
1660                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1661                 // we can just page
1662                 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1663                 if (nextPage != mCurrentPage) {
1664                     snapToPage(nextPage);
1665                 } else {
1666                     snapToDestination();
1667                 }
1668             } else if (mTouchState == TOUCH_STATE_REORDERING) {
1669                 // Update the last motion position
1670                 mLastMotionX = ev.getX();
1671                 mLastMotionY = ev.getY();
1672 
1673                 // Update the parent down so that our zoom animations take this new movement into
1674                 // account
1675                 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1676                 mParentDownMotionX = pt[0];
1677                 mParentDownMotionY = pt[1];
1678                 updateDragViewTranslationDuringDrag();
1679             } else {
1680                 if (!mCancelTap) {
1681                     onUnhandledTap(ev);
1682                 }
1683             }
1684 
1685             // Remove the callback to wait for the side page hover timeout
1686             removeCallbacks(mSidePageHoverRunnable);
1687             // End any intermediate reordering states
1688             resetTouchState();
1689             break;
1690 
1691         case MotionEvent.ACTION_CANCEL:
1692             if (mTouchState == TOUCH_STATE_SCROLLING) {
1693                 snapToDestination();
1694                 onScrollInteractionEnd();
1695             }
1696             resetTouchState();
1697             break;
1698 
1699         case MotionEvent.ACTION_POINTER_UP:
1700             onSecondaryPointerUp(ev);
1701             releaseVelocityTracker();
1702             break;
1703         }
1704 
1705         return true;
1706     }
1707 
resetTouchState()1708     private void resetTouchState() {
1709         releaseVelocityTracker();
1710         endReordering();
1711         mCancelTap = false;
1712         mTouchState = TOUCH_STATE_REST;
1713         mActivePointerId = INVALID_POINTER;
1714         mEdgeGlowLeft.onRelease();
1715         mEdgeGlowRight.onRelease();
1716     }
1717 
1718     /**
1719      * Triggered by scrolling via touch
1720      */
onScrollInteractionBegin()1721     protected void onScrollInteractionBegin() {
1722     }
1723 
onScrollInteractionEnd()1724     protected void onScrollInteractionEnd() {
1725     }
1726 
onUnhandledTap(MotionEvent ev)1727     protected void onUnhandledTap(MotionEvent ev) {
1728         Launcher.getLauncher(getContext()).onClick(this);
1729     }
1730 
1731     @Override
onGenericMotionEvent(MotionEvent event)1732     public boolean onGenericMotionEvent(MotionEvent event) {
1733         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1734             switch (event.getAction()) {
1735                 case MotionEvent.ACTION_SCROLL: {
1736                     // Handle mouse (or ext. device) by shifting the page depending on the scroll
1737                     final float vscroll;
1738                     final float hscroll;
1739                     if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1740                         vscroll = 0;
1741                         hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1742                     } else {
1743                         vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1744                         hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1745                     }
1746                     if (hscroll != 0 || vscroll != 0) {
1747                         boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0)
1748                                                          : (hscroll > 0 || vscroll > 0);
1749                         if (isForwardScroll) {
1750                             scrollRight();
1751                         } else {
1752                             scrollLeft();
1753                         }
1754                         return true;
1755                     }
1756                 }
1757             }
1758         }
1759         return super.onGenericMotionEvent(event);
1760     }
1761 
acquireVelocityTrackerAndAddMovement(MotionEvent ev)1762     private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1763         if (mVelocityTracker == null) {
1764             mVelocityTracker = VelocityTracker.obtain();
1765         }
1766         mVelocityTracker.addMovement(ev);
1767     }
1768 
releaseVelocityTracker()1769     private void releaseVelocityTracker() {
1770         if (mVelocityTracker != null) {
1771             mVelocityTracker.clear();
1772             mVelocityTracker.recycle();
1773             mVelocityTracker = null;
1774         }
1775     }
1776 
onSecondaryPointerUp(MotionEvent ev)1777     private void onSecondaryPointerUp(MotionEvent ev) {
1778         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1779                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1780         final int pointerId = ev.getPointerId(pointerIndex);
1781         if (pointerId == mActivePointerId) {
1782             // This was our active pointer going up. Choose a new
1783             // active pointer and adjust accordingly.
1784             // TODO: Make this decision more intelligent.
1785             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1786             mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1787             mLastMotionY = ev.getY(newPointerIndex);
1788             mLastMotionXRemainder = 0;
1789             mActivePointerId = ev.getPointerId(newPointerIndex);
1790             if (mVelocityTracker != null) {
1791                 mVelocityTracker.clear();
1792             }
1793         }
1794     }
1795 
1796     @Override
requestChildFocus(View child, View focused)1797     public void requestChildFocus(View child, View focused) {
1798         super.requestChildFocus(child, focused);
1799         int page = indexToPage(indexOfChild(child));
1800         if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
1801             snapToPage(page);
1802         }
1803     }
1804 
getPageNearestToCenterOfScreen()1805     int getPageNearestToCenterOfScreen() {
1806         return getPageNearestToCenterOfScreen(getScrollX());
1807     }
1808 
getPageNearestToCenterOfScreen(int scaledScrollX)1809     private int getPageNearestToCenterOfScreen(int scaledScrollX) {
1810         int screenCenter = getViewportOffsetX() + scaledScrollX + (getViewportWidth() / 2);
1811         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
1812         int minDistanceFromScreenCenterIndex = -1;
1813         final int childCount = getChildCount();
1814         for (int i = 0; i < childCount; ++i) {
1815             View layout = (View) getPageAt(i);
1816             int childWidth = layout.getMeasuredWidth();
1817             int halfChildWidth = (childWidth / 2);
1818             int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
1819             int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1820             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1821                 minDistanceFromScreenCenter = distanceFromScreenCenter;
1822                 minDistanceFromScreenCenterIndex = i;
1823             }
1824         }
1825         return minDistanceFromScreenCenterIndex;
1826     }
1827 
snapToDestination()1828     protected void snapToDestination() {
1829         snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION);
1830     }
1831 
1832     public static class ScrollInterpolator implements Interpolator {
ScrollInterpolator()1833         public ScrollInterpolator() {
1834         }
1835 
getInterpolation(float t)1836         public float getInterpolation(float t) {
1837             t -= 1.0f;
1838             return t*t*t*t*t + 1;
1839         }
1840     }
1841 
1842     // We want the duration of the page snap animation to be influenced by the distance that
1843     // the screen has to travel, however, we don't want this duration to be effected in a
1844     // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1845     // of travel has on the overall snap duration.
distanceInfluenceForSnapDuration(float f)1846     private float distanceInfluenceForSnapDuration(float f) {
1847         f -= 0.5f; // center the values about 0.
1848         f *= 0.3f * Math.PI / 2.0f;
1849         return (float) Math.sin(f);
1850     }
1851 
snapToPageWithVelocity(int whichPage, int velocity)1852     protected void snapToPageWithVelocity(int whichPage, int velocity) {
1853         whichPage = validateNewPage(whichPage);
1854         int halfScreenSize = getViewportWidth() / 2;
1855 
1856         final int newX = getScrollForPage(whichPage);
1857         int delta = newX - getUnboundedScrollX();
1858         int duration = 0;
1859 
1860         if (Math.abs(velocity) < mMinFlingVelocity) {
1861             // If the velocity is low enough, then treat this more as an automatic page advance
1862             // as opposed to an apparent physical response to flinging
1863             snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1864             return;
1865         }
1866 
1867         // Here we compute a "distance" that will be used in the computation of the overall
1868         // snap duration. This is a function of the actual distance that needs to be traveled;
1869         // we keep this value close to half screen size in order to reduce the variance in snap
1870         // duration as a function of the distance the page needs to travel.
1871         float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
1872         float distance = halfScreenSize + halfScreenSize *
1873                 distanceInfluenceForSnapDuration(distanceRatio);
1874 
1875         velocity = Math.abs(velocity);
1876         velocity = Math.max(mMinSnapVelocity, velocity);
1877 
1878         // we want the page's snap velocity to approximately match the velocity at which the
1879         // user flings, so we scale the duration by a value near to the derivative of the scroll
1880         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
1881         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1882 
1883         snapToPage(whichPage, delta, duration);
1884     }
1885 
snapToPage(int whichPage)1886     public void snapToPage(int whichPage) {
1887         snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
1888     }
1889 
snapToPageImmediately(int whichPage)1890     public void snapToPageImmediately(int whichPage) {
1891         snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null);
1892     }
1893 
snapToPage(int whichPage, int duration)1894     protected void snapToPage(int whichPage, int duration) {
1895         snapToPage(whichPage, duration, false, null);
1896     }
1897 
snapToPage(int whichPage, int duration, TimeInterpolator interpolator)1898     protected void snapToPage(int whichPage, int duration, TimeInterpolator interpolator) {
1899         snapToPage(whichPage, duration, false, interpolator);
1900     }
1901 
snapToPage(int whichPage, int duration, boolean immediate, TimeInterpolator interpolator)1902     protected void snapToPage(int whichPage, int duration, boolean immediate,
1903             TimeInterpolator interpolator) {
1904         whichPage = validateNewPage(whichPage);
1905 
1906         int newX = getScrollForPage(whichPage);
1907         final int delta = newX - getUnboundedScrollX();
1908         snapToPage(whichPage, delta, duration, immediate, interpolator);
1909     }
1910 
snapToPage(int whichPage, int delta, int duration)1911     protected void snapToPage(int whichPage, int delta, int duration) {
1912         snapToPage(whichPage, delta, duration, false, null);
1913     }
1914 
snapToPage(int whichPage, int delta, int duration, boolean immediate, TimeInterpolator interpolator)1915     protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,
1916             TimeInterpolator interpolator) {
1917         whichPage = validateNewPage(whichPage);
1918 
1919         mNextPage = whichPage;
1920 
1921         awakenScrollBars(duration);
1922         if (immediate) {
1923             duration = 0;
1924         } else if (duration == 0) {
1925             duration = Math.abs(delta);
1926         }
1927 
1928         if (duration != 0) {
1929             pageBeginTransition();
1930         }
1931 
1932         if (!mScroller.isFinished()) {
1933             abortScrollerAnimation(false);
1934         }
1935 
1936         if (interpolator != null) {
1937             mScroller.setInterpolator(interpolator);
1938         } else {
1939             mScroller.setInterpolator(mDefaultInterpolator);
1940         }
1941 
1942         mScroller.startScroll(getUnboundedScrollX(), 0, delta, 0, duration);
1943 
1944         updatePageIndicator();
1945 
1946         // Trigger a compute() to finish switching pages if necessary
1947         if (immediate) {
1948             computeScroll();
1949             pageEndTransition();
1950         }
1951 
1952         invalidate();
1953     }
1954 
scrollLeft()1955     public void scrollLeft() {
1956         if (getNextPage() > 0) snapToPage(getNextPage() - 1);
1957     }
1958 
scrollRight()1959     public void scrollRight() {
1960         if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
1961     }
1962 
1963     @Override
performLongClick()1964     public boolean performLongClick() {
1965         mCancelTap = true;
1966         return super.performLongClick();
1967     }
1968 
1969     public static class SavedState extends BaseSavedState {
1970         int currentPage = -1;
1971 
SavedState(Parcelable superState)1972         SavedState(Parcelable superState) {
1973             super(superState);
1974         }
1975 
SavedState(Parcel in)1976         @Thunk SavedState(Parcel in) {
1977             super(in);
1978             currentPage = in.readInt();
1979         }
1980 
1981         @Override
writeToParcel(Parcel out, int flags)1982         public void writeToParcel(Parcel out, int flags) {
1983             super.writeToParcel(out, flags);
1984             out.writeInt(currentPage);
1985         }
1986 
1987         public static final Parcelable.Creator<SavedState> CREATOR =
1988                 new Parcelable.Creator<SavedState>() {
1989             public SavedState createFromParcel(Parcel in) {
1990                 return new SavedState(in);
1991             }
1992 
1993             public SavedState[] newArray(int size) {
1994                 return new SavedState[size];
1995             }
1996         };
1997     }
1998 
1999     // Animate the drag view back to the original position
animateDragViewToOriginalPosition()2000     private void animateDragViewToOriginalPosition() {
2001         if (mDragView != null) {
2002             Animator anim = LauncherAnimUtils.ofPropertyValuesHolder(mDragView,
2003                     new PropertyListBuilder()
2004                             .scale(1)
2005                             .translationX(0)
2006                             .translationY(0)
2007                             .build())
2008                     .setDuration(REORDERING_DROP_REPOSITION_DURATION);
2009             anim.addListener(new AnimatorListenerAdapter() {
2010                 @Override
2011                 public void onAnimationEnd(Animator animation) {
2012                     onPostReorderingAnimationCompleted();
2013                 }
2014             });
2015             anim.start();
2016         }
2017     }
2018 
onStartReordering()2019     public void onStartReordering() {
2020         // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
2021         mTouchState = TOUCH_STATE_REORDERING;
2022         mIsReordering = true;
2023 
2024         // We must invalidate to trigger a redraw to update the layers such that the drag view
2025         // is always drawn on top
2026         invalidate();
2027     }
2028 
onPostReorderingAnimationCompleted()2029     @Thunk void onPostReorderingAnimationCompleted() {
2030         // Trigger the callback when reordering has settled
2031         --mPostReorderingPreZoomInRemainingAnimationCount;
2032         if (mPostReorderingPreZoomInRunnable != null &&
2033                 mPostReorderingPreZoomInRemainingAnimationCount == 0) {
2034             mPostReorderingPreZoomInRunnable.run();
2035             mPostReorderingPreZoomInRunnable = null;
2036         }
2037     }
2038 
onEndReordering()2039     public void onEndReordering() {
2040         mIsReordering = false;
2041     }
2042 
startReordering(View v)2043     public boolean startReordering(View v) {
2044         int dragViewIndex = indexOfChild(v);
2045 
2046         // Do not allow the first page to be moved around
2047         if (mTouchState != TOUCH_STATE_REST || dragViewIndex <= 0) return false;
2048 
2049         mTempVisiblePagesRange[0] = 0;
2050         mTempVisiblePagesRange[1] = getPageCount() - 1;
2051         getFreeScrollPageRange(mTempVisiblePagesRange);
2052         mReorderingStarted = true;
2053 
2054         // Check if we are within the reordering range
2055         if (mTempVisiblePagesRange[0] <= dragViewIndex &&
2056             dragViewIndex <= mTempVisiblePagesRange[1]) {
2057             // Find the drag view under the pointer
2058             mDragView = getChildAt(dragViewIndex);
2059             mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
2060             mDragViewBaselineLeft = mDragView.getLeft();
2061             snapToPage(getPageNearestToCenterOfScreen());
2062             disableFreeScroll();
2063             onStartReordering();
2064             return true;
2065         }
2066         return false;
2067     }
2068 
isReordering(boolean testTouchState)2069     boolean isReordering(boolean testTouchState) {
2070         boolean state = mIsReordering;
2071         if (testTouchState) {
2072             state &= (mTouchState == TOUCH_STATE_REORDERING);
2073         }
2074         return state;
2075     }
endReordering()2076     void endReordering() {
2077         // For simplicity, we call endReordering sometimes even if reordering was never started.
2078         // In that case, we don't want to do anything.
2079         if (!mReorderingStarted) return;
2080         mReorderingStarted = false;
2081 
2082         mPostReorderingPreZoomInRunnable = new Runnable() {
2083             public void run() {
2084                 // If we haven't flung-to-delete the current child,
2085                 // then we just animate the drag view back into position
2086                 onEndReordering();
2087 
2088                 enableFreeScroll();
2089             }
2090         };
2091 
2092         mPostReorderingPreZoomInRemainingAnimationCount =
2093                 NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
2094         // Snap to the current page
2095         snapToPage(indexOfChild(mDragView), 0);
2096         // Animate the drag view back to the front position
2097         animateDragViewToOriginalPosition();
2098     }
2099 
2100     /* Accessibility */
2101     @SuppressWarnings("deprecation")
2102     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)2103     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2104         super.onInitializeAccessibilityNodeInfo(info);
2105         info.setScrollable(getPageCount() > 1);
2106         if (getCurrentPage() < getPageCount() - 1) {
2107             info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2108         }
2109         if (getCurrentPage() > 0) {
2110             info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2111         }
2112         info.setClassName(getClass().getName());
2113 
2114         // Accessibility-wise, PagedView doesn't support long click, so disabling it.
2115         // Besides disabling the accessibility long-click, this also prevents this view from getting
2116         // accessibility focus.
2117         info.setLongClickable(false);
2118         info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
2119     }
2120 
2121     @Override
sendAccessibilityEvent(int eventType)2122     public void sendAccessibilityEvent(int eventType) {
2123         // Don't let the view send real scroll events.
2124         if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2125             super.sendAccessibilityEvent(eventType);
2126         }
2127     }
2128 
2129     @Override
onInitializeAccessibilityEvent(AccessibilityEvent event)2130     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2131         super.onInitializeAccessibilityEvent(event);
2132         event.setScrollable(getPageCount() > 1);
2133     }
2134 
2135     @Override
performAccessibilityAction(int action, Bundle arguments)2136     public boolean performAccessibilityAction(int action, Bundle arguments) {
2137         if (super.performAccessibilityAction(action, arguments)) {
2138             return true;
2139         }
2140         switch (action) {
2141             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2142                 if (getCurrentPage() < getPageCount() - 1) {
2143                     scrollRight();
2144                     return true;
2145                 }
2146             } break;
2147             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2148                 if (getCurrentPage() > 0) {
2149                     scrollLeft();
2150                     return true;
2151                 }
2152             } break;
2153         }
2154         return false;
2155     }
2156 
getPageIndicatorDescription()2157     protected String getPageIndicatorDescription() {
2158         return getCurrentPageDescription();
2159     }
2160 
getCurrentPageDescription()2161     protected String getCurrentPageDescription() {
2162         return getContext().getString(R.string.default_scroll_format,
2163                 getNextPage() + 1, getChildCount());
2164     }
2165 
2166     @Override
onHoverEvent(android.view.MotionEvent event)2167     public boolean onHoverEvent(android.view.MotionEvent event) {
2168         return true;
2169     }
2170 }
2171