• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.keyguard;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.animation.TimeInterpolator;
24 import android.animation.ValueAnimator;
25 import android.animation.ValueAnimator.AnimatorUpdateListener;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.content.res.TypedArray;
29 import android.graphics.Canvas;
30 import android.graphics.Matrix;
31 import android.graphics.PointF;
32 import android.graphics.Rect;
33 import android.os.Bundle;
34 import android.os.Parcel;
35 import android.os.Parcelable;
36 import android.util.AttributeSet;
37 import android.util.DisplayMetrics;
38 import android.util.Log;
39 import android.view.*;
40 import android.view.accessibility.AccessibilityEvent;
41 import android.view.accessibility.AccessibilityManager;
42 import android.view.accessibility.AccessibilityNodeInfo;
43 import android.view.animation.AccelerateInterpolator;
44 import android.view.animation.AnimationUtils;
45 import android.view.animation.DecelerateInterpolator;
46 import android.view.animation.Interpolator;
47 import android.view.animation.LinearInterpolator;
48 import android.widget.Scroller;
49 
50 import java.util.ArrayList;
51 
52 /**
53  * An abstraction of the original Workspace which supports browsing through a
54  * sequential list of "pages"
55  */
56 public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
57     private static final int WARP_SNAP_DURATION = 160;
58     private static final String TAG = "WidgetPagedView";
59     private static final boolean DEBUG = KeyguardConstants.DEBUG;
60     private static final boolean DEBUG_WARP = false;
61     protected static final int INVALID_PAGE = -1;
62     private static final int WARP_PEEK_ANIMATION_DURATION = 150;
63     private static final float WARP_ANIMATE_AMOUNT = -75.0f; // in dip
64 
65     // the min drag distance for a fling to register, to prevent random page shifts
66     private static final int MIN_LENGTH_FOR_FLING = 25;
67 
68     protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
69     protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
70     protected static final float NANOTIME_DIV = 1000000000.0f;
71 
72     private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
73     private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
74 
75     private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
76     // The page is moved more than halfway, automatically move to the next page on touch up.
77     private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.5f;
78 
79     // The following constants need to be scaled based on density. The scaled versions will be
80     // assigned to the corresponding member variables below.
81     private static final int FLING_THRESHOLD_VELOCITY = 1500;
82     private static final int MIN_SNAP_VELOCITY = 1500;
83     private static final int MIN_FLING_VELOCITY = 500;
84 
85     // We are disabling touch interaction of the widget region for factory ROM.
86     private static final boolean DISABLE_TOUCH_INTERACTION = false;
87     private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
88     private static final boolean DISABLE_FLING_TO_DELETE = false;
89 
90     static final int AUTOMATIC_PAGE_SPACING = -1;
91 
92     protected int mFlingThresholdVelocity;
93     protected int mMinFlingVelocity;
94     protected int mMinSnapVelocity;
95 
96     protected float mDensity;
97     protected float mSmoothingTime;
98     protected float mTouchX;
99 
100     protected boolean mFirstLayout = true;
101 
102     protected int mCurrentPage;
103     protected int mChildCountOnLastMeasure;
104 
105     protected int mNextPage = INVALID_PAGE;
106     protected int mMaxScrollX;
107     protected Scroller mScroller;
108     private VelocityTracker mVelocityTracker;
109 
110     private float mParentDownMotionX;
111     private float mParentDownMotionY;
112     private float mDownMotionX;
113     private float mDownMotionY;
114     private float mDownScrollX;
115     protected float mLastMotionX;
116     protected float mLastMotionXRemainder;
117     protected float mLastMotionY;
118     protected float mTotalMotionX;
119     private int mLastScreenCenter = -1;
120     private int[] mChildOffsets;
121     private int[] mChildRelativeOffsets;
122     private int[] mChildOffsetsWithLayoutScale;
123     private String mDeleteString; // Accessibility announcement when widget is deleted
124 
125     protected final static int TOUCH_STATE_REST = 0;
126     protected final static int TOUCH_STATE_SCROLLING = 1;
127     protected final static int TOUCH_STATE_PREV_PAGE = 2;
128     protected final static int TOUCH_STATE_NEXT_PAGE = 3;
129     protected final static int TOUCH_STATE_REORDERING = 4;
130     protected final static int TOUCH_STATE_READY = 5; // when finger is down
131 
132     protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
133     protected final static float TOUCH_SLOP_SCALE = 1.0f;
134 
135     protected int mTouchState = TOUCH_STATE_REST;
136     protected boolean mForceScreenScrolled = false;
137 
138     protected OnLongClickListener mLongClickListener;
139 
140     protected int mTouchSlop;
141     private int mPagingTouchSlop;
142     private int mMaximumVelocity;
143     private int mMinimumWidth;
144     protected int mPageSpacing;
145     protected int mCellCountX = 0;
146     protected int mCellCountY = 0;
147     protected boolean mAllowOverScroll = true;
148     protected int mUnboundedScrollX;
149     protected int[] mTempVisiblePagesRange = new int[2];
150     protected boolean mForceDrawAllChildrenNextFrame;
151 
152     // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
153     // it is equal to the scaled overscroll position. We use a separate value so as to prevent
154     // the screens from continuing to translate beyond the normal bounds.
155     protected int mOverScrollX;
156 
157     // parameter that adjusts the layout to be optimized for pages with that scale factor
158     protected float mLayoutScale = 1.0f;
159 
160     protected static final int INVALID_POINTER = -1;
161 
162     protected int mActivePointerId = INVALID_POINTER;
163 
164     private PageSwitchListener mPageSwitchListener;
165 
166     protected ArrayList<Boolean> mDirtyPageContent;
167 
168     // If true, syncPages and syncPageItems will be called to refresh pages
169     protected boolean mContentIsRefreshable = true;
170 
171     // If true, modify alpha of neighboring pages as user scrolls left/right
172     protected boolean mFadeInAdjacentScreens = false;
173 
174     // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
175     // to switch to a new page
176     protected boolean mUsePagingTouchSlop = true;
177 
178     // If true, the subclass should directly update scrollX itself in its computeScroll method
179     // (SmoothPagedView does this)
180     protected boolean mDeferScrollUpdate = false;
181 
182     protected boolean mIsPageMoving = false;
183 
184     // All syncs and layout passes are deferred until data is ready.
185     protected boolean mIsDataReady = true;
186 
187     // Scrolling indicator
188     private ValueAnimator mScrollIndicatorAnimator;
189     private View mScrollIndicator;
190     private int mScrollIndicatorPaddingLeft;
191     private int mScrollIndicatorPaddingRight;
192     private boolean mShouldShowScrollIndicator = false;
193     private boolean mShouldShowScrollIndicatorImmediately = false;
194     protected static final int sScrollIndicatorFadeInDuration = 150;
195     protected static final int sScrollIndicatorFadeOutDuration = 650;
196     protected static final int sScrollIndicatorFlashDuration = 650;
197 
198     // The viewport whether the pages are to be contained (the actual view may be larger than the
199     // viewport)
200     private Rect mViewport = new Rect();
201 
202     // Reordering
203     // We use the min scale to determine how much to expand the actually PagedView measured
204     // dimensions such that when we are zoomed out, the view is not clipped
205     private int REORDERING_DROP_REPOSITION_DURATION = 200;
206     protected int REORDERING_REORDER_REPOSITION_DURATION = 300;
207     protected int REORDERING_ZOOM_IN_OUT_DURATION = 250;
208     private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 300;
209     private float REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE = 0.1f;
210     private long REORDERING_DELETE_DROP_TARGET_FADE_DURATION = 150;
211     private float mMinScale = 1f;
212     protected View mDragView;
213     protected AnimatorSet mZoomInOutAnim;
214     private Runnable mSidePageHoverRunnable;
215     private int mSidePageHoverIndex = -1;
216     // This variable's scope is only for the duration of startReordering() and endReordering()
217     private boolean mReorderingStarted = false;
218     // This variable's scope is for the duration of startReordering() and after the zoomIn()
219     // animation after endReordering()
220     private boolean mIsReordering;
221     // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
222     private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
223     private int mPostReorderingPreZoomInRemainingAnimationCount;
224     private Runnable mPostReorderingPreZoomInRunnable;
225 
226     // Edge swiping
227     private boolean mOnlyAllowEdgeSwipes = false;
228     private boolean mDownEventOnEdge = false;
229     private int mEdgeSwipeRegionSize = 0;
230 
231     // Convenience/caching
232     private Matrix mTmpInvMatrix = new Matrix();
233     private float[] mTmpPoint = new float[2];
234     private Rect mTmpRect = new Rect();
235     private Rect mAltTmpRect = new Rect();
236 
237     // Fling to delete
238     private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
239     private float FLING_TO_DELETE_FRICTION = 0.035f;
240     // The degrees specifies how much deviation from the up vector to still consider a fling "up"
241     private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
242     protected int mFlingToDeleteThresholdVelocity = -1400;
243     // Drag to delete
244     private boolean mDeferringForDelete = false;
245     private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
246     private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
247 
248     // Drop to delete
249     private View mDeleteDropTarget;
250 
251     // Bouncer
252     private boolean mTopAlignPageWhenShrinkingForBouncer = false;
253 
254     // Page warping
255     private int mPageSwapIndex = -1; // the page we swapped out if needed
256     private int mPageWarpIndex = -1; // the page we intend to warp
257 
258     private boolean mWarpPageExposed;
259     private ViewPropertyAnimator mWarpAnimation;
260 
261     private boolean mIsCameraEvent;
262     private float mWarpPeekAmount;
263     private boolean mOnPageEndWarpCalled;
264     private boolean mOnPageBeginWarpCalled;
265 
266     public interface PageSwitchListener {
onPageSwitching(View newPage, int newPageIndex)267         void onPageSwitching(View newPage, int newPageIndex);
onPageSwitched(View newPage, int newPageIndex)268         void onPageSwitched(View newPage, int newPageIndex);
269     }
270 
PagedView(Context context)271     public PagedView(Context context) {
272         this(context, null);
273     }
274 
PagedView(Context context, AttributeSet attrs)275     public PagedView(Context context, AttributeSet attrs) {
276         this(context, attrs, 0);
277     }
278 
PagedView(Context context, AttributeSet attrs, int defStyle)279     public PagedView(Context context, AttributeSet attrs, int defStyle) {
280         super(context, attrs, defStyle);
281         TypedArray a = context.obtainStyledAttributes(attrs,
282                 R.styleable.PagedView, defStyle, 0);
283         setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
284         mScrollIndicatorPaddingLeft =
285             a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
286         mScrollIndicatorPaddingRight =
287                 a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
288         a.recycle();
289 
290         Resources r = getResources();
291         mEdgeSwipeRegionSize = r.getDimensionPixelSize(R.dimen.kg_edge_swipe_region_size);
292         mTopAlignPageWhenShrinkingForBouncer =
293                 r.getBoolean(R.bool.kg_top_align_page_shrink_on_bouncer_visible);
294 
295         setHapticFeedbackEnabled(false);
296         init();
297     }
298 
299     /**
300      * Initializes various states for this workspace.
301      */
init()302     protected void init() {
303         mDirtyPageContent = new ArrayList<Boolean>();
304         mDirtyPageContent.ensureCapacity(32);
305         mScroller = new Scroller(getContext(), new ScrollInterpolator());
306         mCurrentPage = 0;
307 
308         final ViewConfiguration configuration = ViewConfiguration.get(getContext());
309         mTouchSlop = configuration.getScaledTouchSlop();
310         mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
311         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
312         mDensity = getResources().getDisplayMetrics().density;
313         mWarpPeekAmount = mDensity * WARP_ANIMATE_AMOUNT;
314 
315         // Scale the fling-to-delete threshold by the density
316         mFlingToDeleteThresholdVelocity = (int) (mFlingToDeleteThresholdVelocity * mDensity);
317 
318         mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
319         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
320         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
321         setOnHierarchyChangeListener(this);
322     }
323 
setDeleteDropTarget(View v)324     void setDeleteDropTarget(View v) {
325         mDeleteDropTarget = v;
326     }
327 
328     // Convenience methods to map points from self to parent and vice versa
mapPointFromViewToParent(View v, float x, float y)329     float[] mapPointFromViewToParent(View v, float x, float y) {
330         mTmpPoint[0] = x;
331         mTmpPoint[1] = y;
332         v.getMatrix().mapPoints(mTmpPoint);
333         mTmpPoint[0] += v.getLeft();
334         mTmpPoint[1] += v.getTop();
335         return mTmpPoint;
336     }
mapPointFromParentToView(View v, float x, float y)337     float[] mapPointFromParentToView(View v, float x, float y) {
338         mTmpPoint[0] = x - v.getLeft();
339         mTmpPoint[1] = y - v.getTop();
340         v.getMatrix().invert(mTmpInvMatrix);
341         mTmpInvMatrix.mapPoints(mTmpPoint);
342         return mTmpPoint;
343     }
344 
updateDragViewTranslationDuringDrag()345     void updateDragViewTranslationDuringDrag() {
346         float x = mLastMotionX - mDownMotionX + getScrollX() - mDownScrollX;
347         float y = mLastMotionY - mDownMotionY;
348         mDragView.setTranslationX(x);
349         mDragView.setTranslationY(y);
350 
351         if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): " + x + ", " + y);
352     }
353 
setMinScale(float f)354     public void setMinScale(float f) {
355         mMinScale = f;
356         requestLayout();
357     }
358 
359     @Override
setScaleX(float scaleX)360     public void setScaleX(float scaleX) {
361         super.setScaleX(scaleX);
362         if (isReordering(true)) {
363             float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
364             mLastMotionX = p[0];
365             mLastMotionY = p[1];
366             updateDragViewTranslationDuringDrag();
367         }
368     }
369 
370     // Convenience methods to get the actual width/height of the PagedView (since it is measured
371     // to be larger to account for the minimum possible scale)
getViewportWidth()372     int getViewportWidth() {
373         return mViewport.width();
374     }
getViewportHeight()375     int getViewportHeight() {
376         return mViewport.height();
377     }
378 
379     // Convenience methods to get the offset ASSUMING that we are centering the pages in the
380     // PagedView both horizontally and vertically
getViewportOffsetX()381     int getViewportOffsetX() {
382         return (getMeasuredWidth() - getViewportWidth()) / 2;
383     }
getViewportOffsetY()384     int getViewportOffsetY() {
385         return (getMeasuredHeight() - getViewportHeight()) / 2;
386     }
387 
setPageSwitchListener(PageSwitchListener pageSwitchListener)388     public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
389         mPageSwitchListener = pageSwitchListener;
390         if (mPageSwitchListener != null) {
391             mPageSwitchListener.onPageSwitched(getPageAt(mCurrentPage), mCurrentPage);
392         }
393     }
394 
395     /**
396      * Called by subclasses to mark that data is ready, and that we can begin loading and laying
397      * out pages.
398      */
setDataIsReady()399     protected void setDataIsReady() {
400         mIsDataReady = true;
401     }
402 
isDataReady()403     protected boolean isDataReady() {
404         return mIsDataReady;
405     }
406 
407     /**
408      * Returns the index of the currently displayed page.
409      *
410      * @return The index of the currently displayed page.
411      */
getCurrentPage()412     int getCurrentPage() {
413         return mCurrentPage;
414     }
415 
getNextPage()416     int getNextPage() {
417         return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
418     }
419 
getPageCount()420     int getPageCount() {
421         return getChildCount();
422     }
423 
getPageAt(int index)424     View getPageAt(int index) {
425         return getChildAt(index);
426     }
427 
indexToPage(int index)428     protected int indexToPage(int index) {
429         return index;
430     }
431 
432     /**
433      * Updates the scroll of the current page immediately to its final scroll position.  We use this
434      * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
435      * the previous tab page.
436      */
updateCurrentPageScroll()437     protected void updateCurrentPageScroll() {
438         int offset = getChildOffset(mCurrentPage);
439         int relOffset = getRelativeChildOffset(mCurrentPage);
440         int newX = offset - relOffset;
441         scrollTo(newX, 0);
442         mScroller.setFinalX(newX);
443         mScroller.forceFinished(true);
444     }
445 
446     /**
447      * Sets the current page.
448      */
setCurrentPage(int currentPage)449     void setCurrentPage(int currentPage) {
450         notifyPageSwitching(currentPage);
451         if (!mScroller.isFinished()) {
452             mScroller.abortAnimation();
453         }
454         // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
455         // the default
456         if (getChildCount() == 0) {
457             return;
458         }
459 
460         mForceScreenScrolled = true;
461         mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
462         updateCurrentPageScroll();
463         updateScrollingIndicator();
464         notifyPageSwitched();
465         invalidate();
466     }
467 
setOnlyAllowEdgeSwipes(boolean enable)468     public void setOnlyAllowEdgeSwipes(boolean enable) {
469         mOnlyAllowEdgeSwipes = enable;
470     }
471 
notifyPageSwitching(int whichPage)472     protected void notifyPageSwitching(int whichPage) {
473         if (mPageSwitchListener != null) {
474             mPageSwitchListener.onPageSwitching(getPageAt(whichPage), whichPage);
475         }
476     }
477 
notifyPageSwitched()478     protected void notifyPageSwitched() {
479         if (mPageSwitchListener != null) {
480             mPageSwitchListener.onPageSwitched(getPageAt(mCurrentPage), mCurrentPage);
481         }
482     }
483 
pageBeginMoving()484     protected void pageBeginMoving() {
485         if (DEBUG_WARP) Log.v(TAG, "pageBeginMoving(" + mIsPageMoving + ")");
486         if (!mIsPageMoving) {
487             mIsPageMoving = true;
488             if (isWarping()) {
489                 dispatchOnPageBeginWarp();
490             }
491             onPageBeginMoving();
492         }
493     }
494 
dispatchOnPageBeginWarp()495     private void dispatchOnPageBeginWarp() {
496         if (!mOnPageBeginWarpCalled) {
497             onPageBeginWarp();
498             mOnPageBeginWarpCalled = true;
499         }
500         mOnPageEndWarpCalled = false;
501     }
502 
dispatchOnPageEndWarp()503     private void dispatchOnPageEndWarp() {
504         if (!mOnPageEndWarpCalled) {
505             onPageEndWarp();
506             mOnPageEndWarpCalled = true;
507         }
508         mOnPageBeginWarpCalled = false;
509     }
510 
pageEndMoving()511     protected void pageEndMoving() {
512         if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")");
513         if (mIsPageMoving) {
514             mIsPageMoving = false;
515             if (isWarping()) {
516                 dispatchOnPageEndWarp();
517                 mWarpPageExposed = false;
518             }
519             onPageEndMoving();
520         }
521     }
522 
isPageMoving()523     protected boolean isPageMoving() {
524         return mIsPageMoving;
525     }
526 
527     // a method that subclasses can override to add behavior
onPageBeginMoving()528     protected void onPageBeginMoving() {
529     }
530 
531     // a method that subclasses can override to add behavior
onPageEndMoving()532     protected void onPageEndMoving() {
533     }
534 
535     /**
536      * Registers the specified listener on each page contained in this workspace.
537      *
538      * @param l The listener used to respond to long clicks.
539      */
540     @Override
setOnLongClickListener(OnLongClickListener l)541     public void setOnLongClickListener(OnLongClickListener l) {
542         mLongClickListener = l;
543         final int count = getPageCount();
544         for (int i = 0; i < count; i++) {
545             getPageAt(i).setOnLongClickListener(l);
546         }
547     }
548 
549     @Override
scrollBy(int x, int y)550     public void scrollBy(int x, int y) {
551         scrollTo(mUnboundedScrollX + x, getScrollY() + y);
552     }
553 
554     @Override
scrollTo(int x, int y)555     public void scrollTo(int x, int y) {
556         mUnboundedScrollX = x;
557 
558         if (x < 0) {
559             super.scrollTo(0, y);
560             if (mAllowOverScroll) {
561                 overScroll(x);
562             }
563         } else if (x > mMaxScrollX) {
564             super.scrollTo(mMaxScrollX, y);
565             if (mAllowOverScroll) {
566                 overScroll(x - mMaxScrollX);
567             }
568         } else {
569             mOverScrollX = x;
570             super.scrollTo(x, y);
571         }
572 
573         mTouchX = x;
574         mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
575 
576         // Update the last motion events when scrolling
577         if (isReordering(true)) {
578             float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
579             mLastMotionX = p[0];
580             mLastMotionY = p[1];
581             updateDragViewTranslationDuringDrag();
582         }
583     }
584 
585     // we moved this functionality to a helper function so SmoothPagedView can reuse it
computeScrollHelper()586     protected boolean computeScrollHelper() {
587         if (mScroller.computeScrollOffset()) {
588             // Don't bother scrolling if the page does not need to be moved
589             if (getScrollX() != mScroller.getCurrX()
590                 || getScrollY() != mScroller.getCurrY()
591                 || mOverScrollX != mScroller.getCurrX()) {
592                 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
593             }
594             invalidate();
595             return true;
596         } else if (mNextPage != INVALID_PAGE) {
597             mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
598             mNextPage = INVALID_PAGE;
599             notifyPageSwitched();
600 
601             // We don't want to trigger a page end moving unless the page has settled
602             // and the user has stopped scrolling
603             if (mTouchState == TOUCH_STATE_REST) {
604                 pageEndMoving();
605             }
606 
607             onPostReorderingAnimationCompleted();
608             return true;
609         }
610         return false;
611     }
612 
613     @Override
computeScroll()614     public void computeScroll() {
615         computeScrollHelper();
616     }
617 
shouldSetTopAlignedPivotForWidget(int childIndex)618     protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
619         return mTopAlignPageWhenShrinkingForBouncer;
620     }
621 
622     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)623     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
624         if (!mIsDataReady || getChildCount() == 0) {
625             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
626             return;
627         }
628 
629         // We measure the dimensions of the PagedView to be larger than the pages so that when we
630         // zoom out (and scale down), the view is still contained in the parent
631         View parent = (View) getParent();
632         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
633         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
634         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
635         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
636         // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the
637         // viewport, we can be at most one and a half screens offset once we scale down
638         DisplayMetrics dm = getResources().getDisplayMetrics();
639         int maxSize = Math.max(dm.widthPixels, dm.heightPixels);
640         int parentWidthSize = (int) (1.5f * maxSize);
641         int parentHeightSize = maxSize;
642         int scaledWidthSize = (int) (parentWidthSize / mMinScale);
643         int scaledHeightSize = (int) (parentHeightSize / mMinScale);
644         mViewport.set(0, 0, widthSize, heightSize);
645 
646         if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
647             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
648             return;
649         }
650 
651         // Return early if we aren't given a proper dimension
652         if (widthSize <= 0 || heightSize <= 0) {
653             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
654             return;
655         }
656 
657         /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
658          * of the All apps view on XLarge displays to not take up more space then it needs. Width
659          * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
660          * each page to have the same width.
661          */
662         final int verticalPadding = getPaddingTop() + getPaddingBottom();
663         final int horizontalPadding = getPaddingLeft() + getPaddingRight();
664 
665         // The children are given the same width and height as the workspace
666         // unless they were set to WRAP_CONTENT
667         if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
668         if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
669         if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
670         if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
671         if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
672         final int childCount = getChildCount();
673         for (int i = 0; i < childCount; i++) {
674             // disallowing padding in paged view (just pass 0)
675             final View child = getPageAt(i);
676             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
677 
678             int childWidthMode;
679             if (lp.width == LayoutParams.WRAP_CONTENT) {
680                 childWidthMode = MeasureSpec.AT_MOST;
681             } else {
682                 childWidthMode = MeasureSpec.EXACTLY;
683             }
684 
685             int childHeightMode;
686             if (lp.height == LayoutParams.WRAP_CONTENT) {
687                 childHeightMode = MeasureSpec.AT_MOST;
688             } else {
689                 childHeightMode = MeasureSpec.EXACTLY;
690             }
691 
692             final int childWidthMeasureSpec =
693                 MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
694             final int childHeightMeasureSpec =
695                 MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
696 
697             child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
698         }
699         setMeasuredDimension(scaledWidthSize, scaledHeightSize);
700 
701         // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
702         // We also wait until we set the measured dimensions before flushing the cache as well, to
703         // ensure that the cache is filled with good values.
704         invalidateCachedOffsets();
705 
706         if (mChildCountOnLastMeasure != getChildCount() && !mDeferringForDelete) {
707             setCurrentPage(mCurrentPage);
708         }
709         mChildCountOnLastMeasure = getChildCount();
710 
711         if (childCount > 0) {
712             if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getViewportWidth() + ", "
713                     + getChildWidth(0));
714 
715             // Calculate the variable page spacing if necessary
716             if (mPageSpacing == AUTOMATIC_PAGE_SPACING) {
717                 // The gap between pages in the PagedView should be equal to the gap from the page
718                 // to the edge of the screen (so it is not visible in the current screen).  To
719                 // account for unequal padding on each side of the paged view, we take the maximum
720                 // of the left/right gap and use that as the gap between each page.
721                 int offset = getRelativeChildOffset(0);
722                 int spacing = Math.max(offset, widthSize - offset -
723                         getChildAt(0).getMeasuredWidth());
724                 setPageSpacing(spacing);
725             }
726         }
727 
728         updateScrollingIndicatorPosition();
729 
730         if (childCount > 0) {
731             mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
732         } else {
733             mMaxScrollX = 0;
734         }
735     }
736 
setPageSpacing(int pageSpacing)737     public void setPageSpacing(int pageSpacing) {
738         mPageSpacing = pageSpacing;
739         invalidateCachedOffsets();
740     }
741 
742     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)743     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
744         if (!mIsDataReady || getChildCount() == 0) {
745             return;
746         }
747 
748         if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
749         final int childCount = getChildCount();
750 
751         int offsetX = getViewportOffsetX();
752         int offsetY = getViewportOffsetY();
753 
754         // Update the viewport offsets
755         mViewport.offset(offsetX,  offsetY);
756 
757         int childLeft = offsetX + getRelativeChildOffset(0);
758         for (int i = 0; i < childCount; i++) {
759             final View child = getPageAt(i);
760             int childTop = offsetY + getPaddingTop();
761             if (child.getVisibility() != View.GONE) {
762                 final int childWidth = getScaledMeasuredWidth(child);
763                 final int childHeight = child.getMeasuredHeight();
764 
765                 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
766                 child.layout(childLeft, childTop,
767                         childLeft + child.getMeasuredWidth(), childTop + childHeight);
768                 childLeft += childWidth + mPageSpacing;
769             }
770         }
771 
772         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
773             setHorizontalScrollBarEnabled(false);
774             updateCurrentPageScroll();
775             setHorizontalScrollBarEnabled(true);
776             mFirstLayout = false;
777         }
778     }
779 
screenScrolled(int screenCenter)780     protected void screenScrolled(int screenCenter) {
781     }
782 
783     @Override
onChildViewAdded(View parent, View child)784     public void onChildViewAdded(View parent, View child) {
785         // This ensures that when children are added, they get the correct transforms / alphas
786         // in accordance with any scroll effects.
787         mForceScreenScrolled = true;
788         invalidate();
789         invalidateCachedOffsets();
790     }
791 
792     @Override
onChildViewRemoved(View parent, View child)793     public void onChildViewRemoved(View parent, View child) {
794         mForceScreenScrolled = true;
795         invalidate();
796         invalidateCachedOffsets();
797     }
798 
invalidateCachedOffsets()799     protected void invalidateCachedOffsets() {
800         int count = getChildCount();
801         if (count == 0) {
802             mChildOffsets = null;
803             mChildRelativeOffsets = null;
804             mChildOffsetsWithLayoutScale = null;
805             return;
806         }
807 
808         mChildOffsets = new int[count];
809         mChildRelativeOffsets = new int[count];
810         mChildOffsetsWithLayoutScale = new int[count];
811         for (int i = 0; i < count; i++) {
812             mChildOffsets[i] = -1;
813             mChildRelativeOffsets[i] = -1;
814             mChildOffsetsWithLayoutScale[i] = -1;
815         }
816     }
817 
getChildOffset(int index)818     protected int getChildOffset(int index) {
819         if (index < 0 || index > getChildCount() - 1) return 0;
820 
821         int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
822                 mChildOffsets : mChildOffsetsWithLayoutScale;
823 
824         if (childOffsets != null && childOffsets[index] != -1) {
825             return childOffsets[index];
826         } else {
827             if (getChildCount() == 0)
828                 return 0;
829 
830             int offset = getRelativeChildOffset(0);
831             for (int i = 0; i < index; ++i) {
832                 offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
833             }
834             if (childOffsets != null) {
835                 childOffsets[index] = offset;
836             }
837             return offset;
838         }
839     }
840 
getRelativeChildOffset(int index)841     protected int getRelativeChildOffset(int index) {
842         if (index < 0 || index > getChildCount() - 1) return 0;
843 
844         if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
845             return mChildRelativeOffsets[index];
846         } else {
847             final int padding = getPaddingLeft() + getPaddingRight();
848             final int offset = getPaddingLeft() +
849                     (getViewportWidth() - padding - getChildWidth(index)) / 2;
850             if (mChildRelativeOffsets != null) {
851                 mChildRelativeOffsets[index] = offset;
852             }
853             return offset;
854         }
855     }
856 
getScaledMeasuredWidth(View child)857     protected int getScaledMeasuredWidth(View child) {
858         // This functions are called enough times that it actually makes a difference in the
859         // profiler -- so just inline the max() here
860         final int measuredWidth = child.getMeasuredWidth();
861         final int minWidth = mMinimumWidth;
862         final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
863         return (int) (maxWidth * mLayoutScale + 0.5f);
864     }
865 
boundByReorderablePages(boolean isReordering, int[] range)866     void boundByReorderablePages(boolean isReordering, int[] range) {
867         // Do nothing
868     }
869 
870     // TODO: Fix this
getVisiblePages(int[] range)871     protected void getVisiblePages(int[] range) {
872         range[0] = 0;
873         range[1] = getPageCount() - 1;
874 
875         /*
876         final int pageCount = getChildCount();
877 
878         if (pageCount > 0) {
879             final int screenWidth = getViewportWidth();
880             int leftScreen = 0;
881             int rightScreen = 0;
882             int offsetX = getViewportOffsetX() + getScrollX();
883             View currPage = getPageAt(leftScreen);
884             while (leftScreen < pageCount - 1 &&
885                     currPage.getX() + currPage.getWidth() -
886                     currPage.getPaddingRight() < offsetX) {
887                 leftScreen++;
888                 currPage = getPageAt(leftScreen);
889             }
890             rightScreen = leftScreen;
891             currPage = getPageAt(rightScreen + 1);
892             while (rightScreen < pageCount - 1 &&
893                     currPage.getX() - currPage.getPaddingLeft() < offsetX + screenWidth) {
894                 rightScreen++;
895                 currPage = getPageAt(rightScreen + 1);
896             }
897 
898             // TEMP: this is a hacky way to ensure that animations to new pages are not clipped
899             // because we don't draw them while scrolling?
900             range[0] = Math.max(0, leftScreen - 1);
901             range[1] = Math.min(rightScreen + 1, getChildCount() - 1);
902         } else {
903             range[0] = -1;
904             range[1] = -1;
905         }
906         */
907     }
908 
shouldDrawChild(View child)909     protected boolean shouldDrawChild(View child) {
910         return child.getAlpha() > 0;
911     }
912 
913     @Override
dispatchDraw(Canvas canvas)914     protected void dispatchDraw(Canvas canvas) {
915         int halfScreenSize = getViewportWidth() / 2;
916         // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
917         // Otherwise it is equal to the scaled overscroll position.
918         int screenCenter = mOverScrollX + halfScreenSize;
919 
920         if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
921             // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
922             // set it for the next frame
923             mForceScreenScrolled = false;
924             screenScrolled(screenCenter);
925             mLastScreenCenter = screenCenter;
926         }
927 
928         // Find out which screens are visible; as an optimization we only call draw on them
929         final int pageCount = getChildCount();
930         if (pageCount > 0) {
931             getVisiblePages(mTempVisiblePagesRange);
932             final int leftScreen = mTempVisiblePagesRange[0];
933             final int rightScreen = mTempVisiblePagesRange[1];
934             if (leftScreen != -1 && rightScreen != -1) {
935                 final long drawingTime = getDrawingTime();
936                 // Clip to the bounds
937                 canvas.save();
938                 canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
939                         getScrollY() + getBottom() - getTop());
940 
941                 // Draw all the children, leaving the drag view for last
942                 for (int i = pageCount - 1; i >= 0; i--) {
943                     final View v = getPageAt(i);
944                     if (v == mDragView) continue;
945                     if (mForceDrawAllChildrenNextFrame ||
946                                (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
947                         drawChild(canvas, v, drawingTime);
948                     }
949                 }
950                 // Draw the drag view on top (if there is one)
951                 if (mDragView != null) {
952                     drawChild(canvas, mDragView, drawingTime);
953                 }
954 
955                 mForceDrawAllChildrenNextFrame = false;
956                 canvas.restore();
957             }
958         }
959     }
960 
961     @Override
requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate)962     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
963         int page = indexToPage(indexOfChild(child));
964         if (page != mCurrentPage || !mScroller.isFinished()) {
965             snapToPage(page);
966             return true;
967         }
968         return false;
969     }
970 
971     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)972     protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
973         int focusablePage;
974         if (mNextPage != INVALID_PAGE) {
975             focusablePage = mNextPage;
976         } else {
977             focusablePage = mCurrentPage;
978         }
979         View v = getPageAt(focusablePage);
980         if (v != null) {
981             return v.requestFocus(direction, previouslyFocusedRect);
982         }
983         return false;
984     }
985 
986     @Override
dispatchUnhandledMove(View focused, int direction)987     public boolean dispatchUnhandledMove(View focused, int direction) {
988         if (direction == View.FOCUS_LEFT) {
989             if (getCurrentPage() > 0) {
990                 snapToPage(getCurrentPage() - 1);
991                 return true;
992             }
993         } else if (direction == View.FOCUS_RIGHT) {
994             if (getCurrentPage() < getPageCount() - 1) {
995                 snapToPage(getCurrentPage() + 1);
996                 return true;
997             }
998         }
999         return super.dispatchUnhandledMove(focused, direction);
1000     }
1001 
1002     @Override
addFocusables(ArrayList<View> views, int direction, int focusableMode)1003     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1004         if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
1005             getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
1006         }
1007         if (direction == View.FOCUS_LEFT) {
1008             if (mCurrentPage > 0) {
1009                 getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
1010             }
1011         } else if (direction == View.FOCUS_RIGHT){
1012             if (mCurrentPage < getPageCount() - 1) {
1013                 getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
1014             }
1015         }
1016     }
1017 
1018     /**
1019      * If one of our descendant views decides that it could be focused now, only
1020      * pass that along if it's on the current page.
1021      *
1022      * This happens when live folders requery, and if they're off page, they
1023      * end up calling requestFocus, which pulls it on page.
1024      */
1025     @Override
focusableViewAvailable(View focused)1026     public void focusableViewAvailable(View focused) {
1027         View current = getPageAt(mCurrentPage);
1028         View v = focused;
1029         while (true) {
1030             if (v == current) {
1031                 super.focusableViewAvailable(focused);
1032                 return;
1033             }
1034             if (v == this) {
1035                 return;
1036             }
1037             ViewParent parent = v.getParent();
1038             if (parent instanceof View) {
1039                 v = (View)v.getParent();
1040             } else {
1041                 return;
1042             }
1043         }
1044     }
1045 
1046     /**
1047      * Return true if a tap at (x, y) should trigger a flip to the previous page.
1048      */
hitsPreviousPage(float x, float y)1049     protected boolean hitsPreviousPage(float x, float y) {
1050         return (x < getViewportOffsetX() + getRelativeChildOffset(mCurrentPage) - mPageSpacing);
1051     }
1052 
1053     /**
1054      * Return true if a tap at (x, y) should trigger a flip to the next page.
1055      */
hitsNextPage(float x, float y)1056     protected boolean hitsNextPage(float x, float y) {
1057         return  (x > (getViewportOffsetX() + getViewportWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
1058     }
1059 
1060     /** Returns whether x and y originated within the buffered viewport */
isTouchPointInViewportWithBuffer(int x, int y)1061     private boolean isTouchPointInViewportWithBuffer(int x, int y) {
1062         mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
1063                 mViewport.right + mViewport.width() / 2, mViewport.bottom);
1064         return mTmpRect.contains(x, y);
1065     }
1066 
1067     /** Returns whether x and y originated within the current page view bounds */
isTouchPointInCurrentPage(int x, int y)1068     private boolean isTouchPointInCurrentPage(int x, int y) {
1069         View v = getPageAt(getCurrentPage());
1070         if (v != null) {
1071             mTmpRect.set((v.getLeft() - getScrollX()), 0, (v.getRight() - getScrollX()),
1072                     v.getBottom());
1073             return mTmpRect.contains(x, y);
1074         }
1075         return false;
1076     }
1077 
1078     @Override
onInterceptTouchEvent(MotionEvent ev)1079     public boolean onInterceptTouchEvent(MotionEvent ev) {
1080         if (DISABLE_TOUCH_INTERACTION) {
1081             return false;
1082         }
1083 
1084         /*
1085          * This method JUST determines whether we want to intercept the motion.
1086          * If we return true, onTouchEvent will be called and we do the actual
1087          * scrolling there.
1088          */
1089         acquireVelocityTrackerAndAddMovement(ev);
1090 
1091         // Skip touch handling if there are no pages to swipe
1092         if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
1093 
1094         /*
1095          * Shortcut the most recurring case: the user is in the dragging
1096          * state and he is moving his finger.  We want to intercept this
1097          * motion.
1098          */
1099         final int action = ev.getAction();
1100         if ((action == MotionEvent.ACTION_MOVE) &&
1101                 (mTouchState == TOUCH_STATE_SCROLLING)) {
1102             return true;
1103         }
1104 
1105         switch (action & MotionEvent.ACTION_MASK) {
1106             case MotionEvent.ACTION_MOVE: {
1107                 /*
1108                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1109                  * whether the user has moved far enough from his original down touch.
1110                  */
1111                 if (mActivePointerId != INVALID_POINTER) {
1112                     if (mIsCameraEvent || determineScrollingStart(ev)) {
1113                         startScrolling(ev);
1114                     }
1115                     break;
1116                 }
1117                 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
1118                 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
1119                 // i.e. fall through to the next case (don't break)
1120                 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
1121                 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
1122 
1123                 break;
1124             }
1125 
1126             case MotionEvent.ACTION_DOWN: {
1127                 if (mIsCameraEvent) {
1128                     animateWarpPageOnScreen("interceptTouch(): DOWN");
1129                 }
1130                 // Remember where the motion event started
1131                 saveDownState(ev);
1132 
1133                 /*
1134                  * If being flinged and user touches the screen, initiate drag;
1135                  * otherwise don't.  mScroller.isFinished should be false when
1136                  * being flinged.
1137                  */
1138                 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
1139                 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
1140                 if (finishedScrolling) {
1141                     setTouchState(TOUCH_STATE_REST);
1142                     mScroller.abortAnimation();
1143                 } else {
1144                     if (mIsCameraEvent || isTouchPointInViewportWithBuffer(
1145                             (int) mDownMotionX, (int) mDownMotionY)) {
1146                         setTouchState(TOUCH_STATE_SCROLLING);
1147                     } else {
1148                         setTouchState(TOUCH_STATE_REST);
1149                     }
1150                 }
1151 
1152                 // check if this can be the beginning of a tap on the side of the pages
1153                 // to scroll the current page
1154                 if (!DISABLE_TOUCH_SIDE_PAGES) {
1155                     if (mTouchState != TOUCH_STATE_PREV_PAGE
1156                             && mTouchState != TOUCH_STATE_NEXT_PAGE) {
1157                         if (getChildCount() > 0) {
1158                             float x = ev.getX();
1159                             float y = ev.getY();
1160                             if (hitsPreviousPage(x, y)) {
1161                                 setTouchState(TOUCH_STATE_PREV_PAGE);
1162                             } else if (hitsNextPage(x, y)) {
1163                                 setTouchState(TOUCH_STATE_NEXT_PAGE);
1164                             }
1165                         }
1166                     }
1167                 }
1168                 break;
1169             }
1170 
1171             case MotionEvent.ACTION_UP:
1172             case MotionEvent.ACTION_CANCEL:
1173                 resetTouchState();
1174                 // Just intercept the touch event on up if we tap outside the strict viewport
1175                 if (!isTouchPointInCurrentPage((int) mLastMotionX, (int) mLastMotionY)) {
1176                     return true;
1177                 }
1178                 break;
1179 
1180             case MotionEvent.ACTION_POINTER_UP:
1181                 onSecondaryPointerUp(ev);
1182                 releaseVelocityTracker();
1183                 break;
1184         }
1185 
1186         /*
1187          * The only time we want to intercept motion events is if we are in the
1188          * drag mode.
1189          */
1190         return mTouchState != TOUCH_STATE_REST;
1191     }
1192 
setTouchState(int touchState)1193     private void setTouchState(int touchState) {
1194         if (mTouchState != touchState) {
1195             if (DEBUG_WARP) Log.v(TAG, "mTouchState changing to " + touchState);
1196             onTouchStateChanged(touchState);
1197             mTouchState = touchState;
1198         }
1199     }
1200 
onTouchStateChanged(int newTouchState)1201     void onTouchStateChanged(int newTouchState) {
1202         if (DEBUG) {
1203             Log.v(TAG, "onTouchStateChanged(old="+ mTouchState + ", new=" + newTouchState + ")");
1204         }
1205     }
1206 
1207     /**
1208      * Save the state when we get {@link MotionEvent#ACTION_DOWN}
1209      * @param ev
1210      */
saveDownState(MotionEvent ev)1211     private void saveDownState(MotionEvent ev) {
1212         // Remember where the motion event started
1213         mDownMotionX = mLastMotionX = ev.getX();
1214         mDownMotionY = mLastMotionY = ev.getY();
1215         mDownScrollX = getScrollX();
1216         float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1217         mParentDownMotionX = p[0];
1218         mParentDownMotionY = p[1];
1219         mLastMotionXRemainder = 0;
1220         mTotalMotionX = 0;
1221         mActivePointerId = ev.getPointerId(0);
1222 
1223         // Determine if the down event is within the threshold to be an edge swipe
1224         int leftEdgeBoundary = getViewportOffsetX() + mEdgeSwipeRegionSize;
1225         int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize;
1226         if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) {
1227             mDownEventOnEdge = true;
1228         }
1229     }
1230 
isHorizontalCameraScroll(MotionEvent ev)1231     private boolean isHorizontalCameraScroll(MotionEvent ev) {
1232         // Disallow scrolling if we don't have a valid pointer index
1233         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1234         if (pointerIndex == -1) return false;
1235 
1236         // If we're only allowing edge swipes, we break out early if the down event wasn't
1237         // at the edge.
1238         if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) return false;
1239 
1240         final float x = ev.getX(pointerIndex);
1241         final int xDiff = (int) Math.abs(x - mDownMotionX);
1242 
1243         final int touchSlop = Math.round(TOUCH_SLOP_SCALE * mTouchSlop);
1244         boolean xPaged = xDiff > mPagingTouchSlop;
1245         boolean xMoved = xDiff > touchSlop;
1246 
1247         return mIsCameraEvent && (mUsePagingTouchSlop ? xPaged : xMoved);
1248     }
1249 
1250     /*
1251      * Determines if we should change the touch state to start scrolling after the
1252      * user moves their touch point too far.
1253      */
determineScrollingStart(MotionEvent ev)1254     protected boolean determineScrollingStart(MotionEvent ev) {
1255         // Disallow scrolling if we don't have a valid pointer index
1256         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1257         if (pointerIndex == -1) return false;
1258 
1259         // Disallow scrolling if we started the gesture from outside the viewport
1260         final float x = ev.getX(pointerIndex);
1261         final float y = ev.getY(pointerIndex);
1262         if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return false;
1263 
1264         // If we're only allowing edge swipes, we break out early if the down event wasn't
1265         // at the edge.
1266         if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) return false;
1267 
1268         final int xDiff = (int) Math.abs(x - mLastMotionX);
1269 
1270         final int touchSlop = Math.round(TOUCH_SLOP_SCALE * mTouchSlop);
1271         boolean xPaged = xDiff > mPagingTouchSlop;
1272         boolean xMoved = xDiff > touchSlop;
1273 
1274         return mUsePagingTouchSlop ? xPaged : xMoved;
1275     }
1276 
startScrolling(MotionEvent ev)1277     private void startScrolling(MotionEvent ev) {
1278         // Ignore if we don't have a valid pointer index
1279         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1280         if (pointerIndex == -1) return;
1281 
1282         final float x = ev.getX(pointerIndex);
1283         setTouchState(TOUCH_STATE_SCROLLING);
1284         mTotalMotionX += Math.abs(mLastMotionX - x);
1285         mLastMotionX = x;
1286         mLastMotionXRemainder = 0;
1287         mTouchX = getViewportOffsetX() + getScrollX();
1288         mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1289         pageBeginMoving();
1290     }
1291 
getMaxScrollProgress()1292     protected float getMaxScrollProgress() {
1293         return 1.0f;
1294     }
1295 
getBoundedScrollProgress(int screenCenter, View v, int page)1296     protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
1297         final int halfScreenSize = getViewportWidth() / 2;
1298 
1299         screenCenter = Math.min(mScrollX + halfScreenSize, screenCenter);
1300         screenCenter = Math.max(halfScreenSize,  screenCenter);
1301 
1302         return getScrollProgress(screenCenter, v, page);
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 totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
1309         int delta = screenCenter - (getChildOffset(page) -
1310                 getRelativeChildOffset(page) + halfScreenSize);
1311 
1312         float scrollProgress = delta / (totalDistance * 1.0f);
1313         scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
1314         scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
1315         return scrollProgress;
1316     }
1317 
1318     // This curve determines how the effect of scrolling over the limits of the page dimishes
1319     // as the user pulls further and further from the bounds
overScrollInfluenceCurve(float f)1320     private float overScrollInfluenceCurve(float f) {
1321         f -= 1.0f;
1322         return f * f * f + 1.0f;
1323     }
1324 
acceleratedOverScroll(float amount)1325     protected void acceleratedOverScroll(float amount) {
1326         int screenSize = getViewportWidth();
1327 
1328         // We want to reach the max over scroll effect when the user has
1329         // over scrolled half the size of the screen
1330         float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1331 
1332         if (f == 0) return;
1333 
1334         // Clamp this factor, f, to -1 < f < 1
1335         if (Math.abs(f) >= 1) {
1336             f /= Math.abs(f);
1337         }
1338 
1339         int overScrollAmount = (int) Math.round(f * screenSize);
1340         if (amount < 0) {
1341             mOverScrollX = overScrollAmount;
1342             super.scrollTo(0, getScrollY());
1343         } else {
1344             mOverScrollX = mMaxScrollX + overScrollAmount;
1345             super.scrollTo(mMaxScrollX, getScrollY());
1346         }
1347         invalidate();
1348     }
1349 
dampedOverScroll(float amount)1350     protected void dampedOverScroll(float amount) {
1351         int screenSize = getViewportWidth();
1352 
1353         float f = (amount / screenSize);
1354 
1355         if (f == 0) return;
1356         f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1357 
1358         // Clamp this factor, f, to -1 < f < 1
1359         if (Math.abs(f) >= 1) {
1360             f /= Math.abs(f);
1361         }
1362 
1363         int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
1364         if (amount < 0) {
1365             mOverScrollX = overScrollAmount;
1366             super.scrollTo(0, getScrollY());
1367         } else {
1368             mOverScrollX = mMaxScrollX + overScrollAmount;
1369             super.scrollTo(mMaxScrollX, getScrollY());
1370         }
1371         invalidate();
1372     }
1373 
overScroll(float amount)1374     protected void overScroll(float amount) {
1375         dampedOverScroll(amount);
1376     }
1377 
maxOverScroll()1378     protected float maxOverScroll() {
1379         // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1380         // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1381         float f = 1.0f;
1382         f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1383         return OVERSCROLL_DAMP_FACTOR * f;
1384     }
1385 
1386     @Override
onTouchEvent(MotionEvent ev)1387     public boolean onTouchEvent(MotionEvent ev) {
1388         if (DISABLE_TOUCH_INTERACTION) {
1389             return false;
1390         }
1391 
1392         // Skip touch handling if there are no pages to swipe
1393         if (getChildCount() <= 0) return super.onTouchEvent(ev);
1394 
1395         acquireVelocityTrackerAndAddMovement(ev);
1396 
1397         final int action = ev.getAction();
1398 
1399         switch (action & MotionEvent.ACTION_MASK) {
1400         case MotionEvent.ACTION_DOWN:
1401             /*
1402              * If being flinged and user touches, stop the fling. isFinished
1403              * will be false if being flinged.
1404              */
1405             if (!mScroller.isFinished()) {
1406                 mScroller.abortAnimation();
1407             }
1408 
1409             // Remember where the motion event started
1410             saveDownState(ev);
1411 
1412             if (mTouchState == TOUCH_STATE_SCROLLING) {
1413                 pageBeginMoving();
1414             } else {
1415                 setTouchState(TOUCH_STATE_READY);
1416             }
1417 
1418             if (mIsCameraEvent) {
1419                 animateWarpPageOnScreen("onTouch(): DOWN");
1420             }
1421             break;
1422 
1423         case MotionEvent.ACTION_MOVE:
1424             if (mTouchState == TOUCH_STATE_SCROLLING) {
1425                 // Scroll to follow the motion event
1426                 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1427 
1428                 if (pointerIndex == -1) return true;
1429 
1430                 final float x = ev.getX(pointerIndex);
1431                 final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1432 
1433                 mTotalMotionX += Math.abs(deltaX);
1434 
1435                 // Only scroll and update mLastMotionX if we have moved some discrete amount.  We
1436                 // keep the remainder because we are actually testing if we've moved from the last
1437                 // scrolled position (which is discrete).
1438                 if (Math.abs(deltaX) >= 1.0f) {
1439                     mTouchX += deltaX;
1440                     mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1441                     if (isWarping()) {
1442                         KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
1443                         v.setTranslationX(v.getTranslationX() - deltaX);
1444                     } else if (!mDeferScrollUpdate) {
1445                         scrollBy((int) deltaX, 0);
1446                         if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
1447                     } else {
1448                         invalidate();
1449                     }
1450                     mLastMotionX = x;
1451                     mLastMotionXRemainder = deltaX - (int) deltaX;
1452                 } else {
1453                     awakenScrollBars();
1454                 }
1455             } else if (mTouchState == TOUCH_STATE_REORDERING) {
1456                 // Update the last motion position
1457                 mLastMotionX = ev.getX();
1458                 mLastMotionY = ev.getY();
1459 
1460                 // Update the parent down so that our zoom animations take this new movement into
1461                 // account
1462                 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1463                 mParentDownMotionX = pt[0];
1464                 mParentDownMotionY = pt[1];
1465                 updateDragViewTranslationDuringDrag();
1466 
1467                 // Find the closest page to the touch point
1468                 final int dragViewIndex = indexOfChild(mDragView);
1469                 int bufferSize = (int) (REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE *
1470                     getViewportWidth());
1471                 int leftBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.left, 0)[0]
1472                         + bufferSize);
1473                 int rightBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.right, 0)[0]
1474                         - bufferSize);
1475 
1476                 // Change the drag view if we are hovering over the drop target
1477                 boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
1478                         (int) mParentDownMotionX, (int) mParentDownMotionY);
1479                 setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
1480 
1481                 if (DEBUG) Log.d(TAG, "leftBufferEdge: " + leftBufferEdge);
1482                 if (DEBUG) Log.d(TAG, "rightBufferEdge: " + rightBufferEdge);
1483                 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
1484                 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
1485                 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
1486                 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
1487 
1488                 float parentX = mParentDownMotionX;
1489                 int pageIndexToSnapTo = -1;
1490                 if (parentX < leftBufferEdge && dragViewIndex > 0) {
1491                     pageIndexToSnapTo = dragViewIndex - 1;
1492                 } else if (parentX > rightBufferEdge && dragViewIndex < getChildCount() - 1) {
1493                     pageIndexToSnapTo = dragViewIndex + 1;
1494                 }
1495 
1496                 final int pageUnderPointIndex = pageIndexToSnapTo;
1497                 if (pageUnderPointIndex > -1 && !isHoveringOverDelete) {
1498                     mTempVisiblePagesRange[0] = 0;
1499                     mTempVisiblePagesRange[1] = getPageCount() - 1;
1500                     boundByReorderablePages(true, mTempVisiblePagesRange);
1501                     if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
1502                             pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
1503                             pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
1504                         mSidePageHoverIndex = pageUnderPointIndex;
1505                         mSidePageHoverRunnable = new Runnable() {
1506                             @Override
1507                             public void run() {
1508                                 // Update the down scroll position to account for the fact that the
1509                                 // current page is moved
1510                                 mDownScrollX = getChildOffset(pageUnderPointIndex)
1511                                         - getRelativeChildOffset(pageUnderPointIndex);
1512 
1513                                 // Setup the scroll to the correct page before we swap the views
1514                                 snapToPage(pageUnderPointIndex);
1515 
1516                                 // For each of the pages between the paged view and the drag view,
1517                                 // animate them from the previous position to the new position in
1518                                 // the layout (as a result of the drag view moving in the layout)
1519                                 int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
1520                                 int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
1521                                         dragViewIndex + 1 : pageUnderPointIndex;
1522                                 int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
1523                                         dragViewIndex - 1 : pageUnderPointIndex;
1524                                 for (int i = lowerIndex; i <= upperIndex; ++i) {
1525                                     View v = getChildAt(i);
1526                                     // dragViewIndex < pageUnderPointIndex, so after we remove the
1527                                     // drag view all subsequent views to pageUnderPointIndex will
1528                                     // shift down.
1529                                     int oldX = getViewportOffsetX() + getChildOffset(i);
1530                                     int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
1531 
1532                                     // Animate the view translation from its old position to its new
1533                                     // position
1534                                     AnimatorSet anim = (AnimatorSet) v.getTag();
1535                                     if (anim != null) {
1536                                         anim.cancel();
1537                                     }
1538 
1539                                     v.setTranslationX(oldX - newX);
1540                                     anim = new AnimatorSet();
1541                                     anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
1542                                     anim.playTogether(
1543                                             ObjectAnimator.ofFloat(v, "translationX", 0f));
1544                                     anim.start();
1545                                     v.setTag(anim);
1546                                 }
1547 
1548                                 removeView(mDragView);
1549                                 onRemoveView(mDragView, false);
1550                                 addView(mDragView, pageUnderPointIndex);
1551                                 onAddView(mDragView, pageUnderPointIndex);
1552                                 mSidePageHoverIndex = -1;
1553                             }
1554                         };
1555                         postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
1556                     }
1557                 } else {
1558                     removeCallbacks(mSidePageHoverRunnable);
1559                     mSidePageHoverIndex = -1;
1560                 }
1561             } else if (determineScrollingStart(ev)) {
1562                 startScrolling(ev);
1563             } else if (isHorizontalCameraScroll(ev)) {
1564                 startScrolling(ev);
1565                 // we need to cancel the camera animation
1566                 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
1567                 v.animate().cancel();
1568             }
1569             break;
1570 
1571         case MotionEvent.ACTION_UP:
1572             if (mTouchState == TOUCH_STATE_SCROLLING) {
1573                 final int activePointerId = mActivePointerId;
1574                 final int pointerIndex = ev.findPointerIndex(activePointerId);
1575 
1576                 if (pointerIndex == -1) return true;
1577 
1578                 final float x = ev.getX(pointerIndex);
1579                 final VelocityTracker velocityTracker = mVelocityTracker;
1580                 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1581                 int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1582                 final int deltaX = (int) (x - mDownMotionX);
1583                 final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
1584                 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1585                         SIGNIFICANT_MOVE_THRESHOLD;
1586 
1587                 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1588 
1589                 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1590                         Math.abs(velocityX) > mFlingThresholdVelocity;
1591 
1592                 // In the case that the page is moved far to one direction and then is flung
1593                 // in the opposite direction, we use a threshold to determine whether we should
1594                 // just return to the starting page, or if we should skip one further.
1595                 boolean returnToOriginalPage = false;
1596                 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1597                         Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1598                     returnToOriginalPage = true;
1599                 }
1600 
1601                 int finalPage;
1602                 // We give flings precedence over large moves, which is why we short-circuit our
1603                 // test for a large move if a fling has been registered. That is, a large
1604                 // move to the left and fling to the right will register as a fling to the right.
1605                 if (((isSignificantMove && deltaX > 0 && !isFling) ||
1606                         (isFling && velocityX > 0)) && mCurrentPage > 0) {
1607                     finalPage = returnToOriginalPage || isWarping()
1608                             ? mCurrentPage : mCurrentPage - 1;
1609                     snapToPageWithVelocity(finalPage, velocityX);
1610                 } else if (((isSignificantMove && deltaX < 0 && !isFling) ||
1611                         (isFling && velocityX < 0)) &&
1612                         mCurrentPage < getChildCount() - 1) {
1613                     finalPage = returnToOriginalPage ? mCurrentPage :
1614                             isWarping() ? getPageWarpIndex() : mCurrentPage + 1;
1615                     snapToPageWithVelocity(finalPage, velocityX);
1616                 } else {
1617                     snapToDestination();
1618                 }
1619             } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1620                 // at this point we have not moved beyond the touch slop
1621                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1622                 // we can just page
1623                 int nextPage = Math.max(0, mCurrentPage - 1);
1624                 if (nextPage != mCurrentPage) {
1625                     snapToPage(nextPage);
1626                 } else {
1627                     snapToDestination();
1628                 }
1629             } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1630                 // at this point we have not moved beyond the touch slop
1631                 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1632                 // we can just page
1633                 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1634                 if (nextPage != mCurrentPage) {
1635                     snapToPage(nextPage);
1636                 } else {
1637                     snapToDestination();
1638                 }
1639             } else if (mTouchState == TOUCH_STATE_REORDERING) {
1640                 // Update the last motion position
1641                 mLastMotionX = ev.getX();
1642                 mLastMotionY = ev.getY();
1643 
1644                 // Update the parent down so that our zoom animations take this new movement into
1645                 // account
1646                 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1647                 mParentDownMotionX = pt[0];
1648                 mParentDownMotionY = pt[1];
1649                 updateDragViewTranslationDuringDrag();
1650                 boolean handledFling = false;
1651                 if (!DISABLE_FLING_TO_DELETE) {
1652                     // Check the velocity and see if we are flinging-to-delete
1653                     PointF flingToDeleteVector = isFlingingToDelete();
1654                     if (flingToDeleteVector != null) {
1655                         onFlingToDelete(flingToDeleteVector);
1656                         handledFling = true;
1657                     }
1658                 }
1659                 if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
1660                         (int) mParentDownMotionY)) {
1661                     onDropToDelete();
1662                 }
1663             } else {
1664                 if (DEBUG_WARP) Log.v(TAG, "calling onUnhandledTap()");
1665                 if (mWarpPageExposed && !isAnimatingWarpPage()) {
1666                     animateWarpPageOffScreen("unhandled tap", true);
1667                 }
1668                 onUnhandledTap(ev);
1669             }
1670 
1671             // Remove the callback to wait for the side page hover timeout
1672             removeCallbacks(mSidePageHoverRunnable);
1673             // End any intermediate reordering states
1674             resetTouchState();
1675             break;
1676 
1677         case MotionEvent.ACTION_CANCEL:
1678             if (mTouchState == TOUCH_STATE_SCROLLING) {
1679                 snapToDestination();
1680             }
1681             resetTouchState();
1682             break;
1683 
1684         case MotionEvent.ACTION_POINTER_UP:
1685             onSecondaryPointerUp(ev);
1686             break;
1687         }
1688 
1689         return true;
1690     }
1691 
1692     //public abstract void onFlingToDelete(View v);
onRemoveView(View v, boolean deletePermanently)1693     public abstract void onRemoveView(View v, boolean deletePermanently);
onRemoveViewAnimationCompleted()1694     public abstract void onRemoveViewAnimationCompleted();
onAddView(View v, int index)1695     public abstract void onAddView(View v, int index);
1696 
resetTouchState()1697     private void resetTouchState() {
1698         releaseVelocityTracker();
1699         endReordering();
1700         setTouchState(TOUCH_STATE_REST);
1701         mActivePointerId = INVALID_POINTER;
1702         mDownEventOnEdge = false;
1703     }
1704 
onUnhandledTap(MotionEvent ev)1705     protected void onUnhandledTap(MotionEvent ev) {}
1706 
1707     @Override
onGenericMotionEvent(MotionEvent event)1708     public boolean onGenericMotionEvent(MotionEvent event) {
1709         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1710             switch (event.getAction()) {
1711                 case MotionEvent.ACTION_SCROLL: {
1712                     // Handle mouse (or ext. device) by shifting the page depending on the scroll
1713                     final float vscroll;
1714                     final float hscroll;
1715                     if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1716                         vscroll = 0;
1717                         hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1718                     } else {
1719                         vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1720                         hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1721                     }
1722                     if (hscroll != 0 || vscroll != 0) {
1723                         if (hscroll > 0 || vscroll > 0) {
1724                             scrollRight();
1725                         } else {
1726                             scrollLeft();
1727                         }
1728                         return true;
1729                     }
1730                 }
1731             }
1732         }
1733         return super.onGenericMotionEvent(event);
1734     }
1735 
acquireVelocityTrackerAndAddMovement(MotionEvent ev)1736     private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1737         if (mVelocityTracker == null) {
1738             mVelocityTracker = VelocityTracker.obtain();
1739         }
1740         mVelocityTracker.addMovement(ev);
1741     }
1742 
releaseVelocityTracker()1743     private void releaseVelocityTracker() {
1744         if (mVelocityTracker != null) {
1745             mVelocityTracker.recycle();
1746             mVelocityTracker = null;
1747         }
1748     }
1749 
onSecondaryPointerUp(MotionEvent ev)1750     private void onSecondaryPointerUp(MotionEvent ev) {
1751         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1752                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1753         final int pointerId = ev.getPointerId(pointerIndex);
1754         if (pointerId == mActivePointerId) {
1755             // This was our active pointer going up. Choose a new
1756             // active pointer and adjust accordingly.
1757             // TODO: Make this decision more intelligent.
1758             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1759             mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1760             mLastMotionY = ev.getY(newPointerIndex);
1761             mLastMotionXRemainder = 0;
1762             mActivePointerId = ev.getPointerId(newPointerIndex);
1763             if (mVelocityTracker != null) {
1764                 mVelocityTracker.clear();
1765             }
1766         }
1767     }
1768 
1769     @Override
requestChildFocus(View child, View focused)1770     public void requestChildFocus(View child, View focused) {
1771         super.requestChildFocus(child, focused);
1772         int page = indexToPage(indexOfChild(child));
1773         if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
1774             snapToPage(page);
1775         }
1776     }
1777 
getChildIndexForRelativeOffset(int relativeOffset)1778     protected int getChildIndexForRelativeOffset(int relativeOffset) {
1779         final int childCount = getChildCount();
1780         int left;
1781         int right;
1782         for (int i = 0; i < childCount; ++i) {
1783             left = getRelativeChildOffset(i);
1784             right = (left + getScaledMeasuredWidth(getPageAt(i)));
1785             if (left <= relativeOffset && relativeOffset <= right) {
1786                 return i;
1787             }
1788         }
1789         return -1;
1790     }
1791 
getChildWidth(int index)1792     protected int getChildWidth(int index) {
1793         // This functions are called enough times that it actually makes a difference in the
1794         // profiler -- so just inline the max() here
1795         final int measuredWidth = getPageAt(index).getMeasuredWidth();
1796         final int minWidth = mMinimumWidth;
1797         return (minWidth > measuredWidth) ? minWidth : measuredWidth;
1798     }
1799 
getPageNearestToPoint(float x)1800     int getPageNearestToPoint(float x) {
1801         int index = 0;
1802         for (int i = 0; i < getChildCount(); ++i) {
1803             if (x < getChildAt(i).getRight() - getScrollX()) {
1804                 return index;
1805             } else {
1806                 index++;
1807             }
1808         }
1809         return Math.min(index, getChildCount() - 1);
1810     }
1811 
getPageNearestToCenterOfScreen()1812     int getPageNearestToCenterOfScreen() {
1813         int minDistanceFromScreenCenter = Integer.MAX_VALUE;
1814         int minDistanceFromScreenCenterIndex = -1;
1815         int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
1816         final int childCount = getChildCount();
1817         for (int i = 0; i < childCount; ++i) {
1818             View layout = (View) getPageAt(i);
1819             int childWidth = getScaledMeasuredWidth(layout);
1820             int halfChildWidth = (childWidth / 2);
1821             int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
1822             int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1823             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1824                 minDistanceFromScreenCenter = distanceFromScreenCenter;
1825                 minDistanceFromScreenCenterIndex = i;
1826             }
1827         }
1828         return minDistanceFromScreenCenterIndex;
1829     }
1830 
snapToDestination()1831     protected void snapToDestination() {
1832         final int newPage = getPageNearestToCenterOfScreen();
1833         if (isWarping()) {
1834             cancelWarpAnimation("snapToDestination", mCurrentPage != newPage);
1835         }
1836         snapToPage(newPage, getPageSnapDuration());
1837     }
1838 
getPageSnapDuration()1839     private int getPageSnapDuration() {
1840         return isWarping() ? WARP_SNAP_DURATION : PAGE_SNAP_ANIMATION_DURATION;
1841     }
1842 
1843     private static class ScrollInterpolator implements Interpolator {
ScrollInterpolator()1844         public ScrollInterpolator() {
1845         }
1846 
getInterpolation(float t)1847         public float getInterpolation(float t) {
1848             t -= 1.0f;
1849             return t*t*t*t*t + 1;
1850         }
1851     }
1852 
1853     // We want the duration of the page snap animation to be influenced by the distance that
1854     // the screen has to travel, however, we don't want this duration to be effected in a
1855     // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1856     // of travel has on the overall snap duration.
distanceInfluenceForSnapDuration(float f)1857     float distanceInfluenceForSnapDuration(float f) {
1858         f -= 0.5f; // center the values about 0.
1859         f *= 0.3f * Math.PI / 2.0f;
1860         return (float) Math.sin(f);
1861     }
1862 
snapToPageWithVelocity(int whichPage, int velocity)1863     protected void snapToPageWithVelocity(int whichPage, int velocity) {
1864         whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
1865         int halfScreenSize = getViewportWidth() / 2;
1866 
1867         if (isWarping()) {
1868             cancelWarpAnimation("snapToPageWithVelocity", mCurrentPage != whichPage);
1869         }
1870 
1871         if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1872         if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
1873                 + getViewportWidth() + ", " + getChildWidth(whichPage));
1874         final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1875         int delta = newX - mUnboundedScrollX;
1876         int duration = 0;
1877 
1878         if (Math.abs(velocity) < mMinFlingVelocity) {
1879             // If the velocity is low enough, then treat this more as an automatic page advance
1880             // as opposed to an apparent physical response to flinging
1881             snapToPage(whichPage, getPageSnapDuration());
1882             return;
1883         }
1884 
1885         // Here we compute a "distance" that will be used in the computation of the overall
1886         // snap duration. This is a function of the actual distance that needs to be traveled;
1887         // we keep this value close to half screen size in order to reduce the variance in snap
1888         // duration as a function of the distance the page needs to travel.
1889         float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
1890         float distance = halfScreenSize + halfScreenSize *
1891                 distanceInfluenceForSnapDuration(distanceRatio);
1892 
1893         velocity = Math.abs(velocity);
1894         velocity = Math.max(mMinSnapVelocity, velocity);
1895 
1896         // we want the page's snap velocity to approximately match the velocity at which the
1897         // user flings, so we scale the duration by a value near to the derivative of the scroll
1898         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
1899         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1900 
1901         snapToPage(whichPage, delta, duration);
1902     }
1903 
snapToPage(int whichPage)1904     protected void snapToPage(int whichPage) {
1905         snapToPage(whichPage, getPageSnapDuration());
1906     }
snapToPageImmediately(int whichPage)1907     protected void snapToPageImmediately(int whichPage) {
1908         snapToPage(whichPage, getPageSnapDuration(), true);
1909     }
1910 
snapToPage(int whichPage, int duration)1911     protected void snapToPage(int whichPage, int duration) {
1912         snapToPage(whichPage, duration, false);
1913     }
snapToPage(int whichPage, int duration, boolean immediate)1914     protected void snapToPage(int whichPage, int duration, boolean immediate) {
1915         whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1916 
1917         if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1918         if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getViewportWidth() + ", "
1919                 + getChildWidth(whichPage));
1920         int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1921         final int sX = mUnboundedScrollX;
1922         final int delta = newX - sX;
1923         snapToPage(whichPage, delta, duration, immediate);
1924     }
1925 
snapToPage(int whichPage, int delta, int duration)1926     protected void snapToPage(int whichPage, int delta, int duration) {
1927         snapToPage(whichPage, delta, duration, false);
1928     }
1929 
snapToPage(int whichPage, int delta, int duration, boolean immediate)1930     protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
1931         if (isWarping() && whichPage == mCurrentPage+1) {
1932             mNextPage = getPageWarpIndex(); // jump to the warp page
1933             if (DEBUG_WARP) Log.v(TAG, "snapToPage(" + whichPage + ") : reset mPageSwapIndex");
1934         } else {
1935             mNextPage = whichPage;
1936         }
1937 
1938         if(mWarpPageExposed) {
1939             dispatchOnPageEndWarp();
1940             mWarpPageExposed = false;
1941         }
1942         notifyPageSwitching(whichPage);
1943 
1944 
1945         View focusedChild = getFocusedChild();
1946         if (focusedChild != null && whichPage != mCurrentPage &&
1947                 focusedChild == getPageAt(mCurrentPage)) {
1948             focusedChild.clearFocus();
1949         }
1950 
1951         pageBeginMoving();
1952         awakenScrollBars(duration);
1953         if (immediate) {
1954             duration = 0;
1955         } else if (duration == 0) {
1956             duration = Math.abs(delta);
1957         }
1958 
1959         if (!mScroller.isFinished()) mScroller.abortAnimation();
1960         mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
1961 
1962         notifyPageSwitched();
1963 
1964         // Trigger a compute() to finish switching pages if necessary
1965         if (immediate) {
1966             computeScroll();
1967         }
1968 
1969         mForceScreenScrolled = true;
1970         invalidate();
1971     }
1972 
isWarping()1973     protected boolean isWarping() {
1974         return mWarpPageExposed;
1975     }
1976 
scrollLeft()1977     public void scrollLeft() {
1978         if (mScroller.isFinished()) {
1979             if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
1980         } else {
1981             if (mNextPage > 0) snapToPage(mNextPage - 1);
1982         }
1983     }
1984 
scrollRight()1985     public void scrollRight() {
1986         if (mScroller.isFinished()) {
1987             if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
1988         } else {
1989             if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
1990         }
1991     }
1992 
getPageForView(View v)1993     public int getPageForView(View v) {
1994         int result = -1;
1995         if (v != null) {
1996             ViewParent vp = v.getParent();
1997             int count = getChildCount();
1998             for (int i = 0; i < count; i++) {
1999                 if (vp == getPageAt(i)) {
2000                     return i;
2001                 }
2002             }
2003         }
2004         return result;
2005     }
2006 
2007     public static class SavedState extends BaseSavedState {
2008         int currentPage = -1;
2009 
SavedState(Parcelable superState)2010         SavedState(Parcelable superState) {
2011             super(superState);
2012         }
2013 
SavedState(Parcel in)2014         private SavedState(Parcel in) {
2015             super(in);
2016             currentPage = in.readInt();
2017         }
2018 
2019         @Override
writeToParcel(Parcel out, int flags)2020         public void writeToParcel(Parcel out, int flags) {
2021             super.writeToParcel(out, flags);
2022             out.writeInt(currentPage);
2023         }
2024 
2025         public static final Parcelable.Creator<SavedState> CREATOR =
2026                 new Parcelable.Creator<SavedState>() {
2027             public SavedState createFromParcel(Parcel in) {
2028                 return new SavedState(in);
2029             }
2030 
2031             public SavedState[] newArray(int size) {
2032                 return new SavedState[size];
2033             }
2034         };
2035     }
2036 
getScrollingIndicator()2037     protected View getScrollingIndicator() {
2038         return null;
2039     }
2040 
isScrollingIndicatorEnabled()2041     protected boolean isScrollingIndicatorEnabled() {
2042         return false;
2043     }
2044 
2045     Runnable hideScrollingIndicatorRunnable = new Runnable() {
2046         @Override
2047         public void run() {
2048             hideScrollingIndicator(false);
2049         }
2050     };
2051 
flashScrollingIndicator(boolean animated)2052     protected void flashScrollingIndicator(boolean animated) {
2053         removeCallbacks(hideScrollingIndicatorRunnable);
2054         showScrollingIndicator(!animated);
2055         postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
2056     }
2057 
showScrollingIndicator(boolean immediately)2058     protected void showScrollingIndicator(boolean immediately) {
2059         mShouldShowScrollIndicator = true;
2060         mShouldShowScrollIndicatorImmediately = true;
2061         if (getChildCount() <= 1) return;
2062         if (!isScrollingIndicatorEnabled()) return;
2063 
2064         mShouldShowScrollIndicator = false;
2065         getScrollingIndicator();
2066         if (mScrollIndicator != null) {
2067             // Fade the indicator in
2068             updateScrollingIndicatorPosition();
2069             mScrollIndicator.setVisibility(View.VISIBLE);
2070             cancelScrollingIndicatorAnimations();
2071             if (immediately) {
2072                 mScrollIndicator.setAlpha(1f);
2073             } else {
2074                 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f);
2075                 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
2076                 mScrollIndicatorAnimator.start();
2077             }
2078         }
2079     }
2080 
cancelScrollingIndicatorAnimations()2081     protected void cancelScrollingIndicatorAnimations() {
2082         if (mScrollIndicatorAnimator != null) {
2083             mScrollIndicatorAnimator.cancel();
2084         }
2085     }
2086 
hideScrollingIndicator(boolean immediately)2087     protected void hideScrollingIndicator(boolean immediately) {
2088         if (getChildCount() <= 1) return;
2089         if (!isScrollingIndicatorEnabled()) return;
2090 
2091         getScrollingIndicator();
2092         if (mScrollIndicator != null) {
2093             // Fade the indicator out
2094             updateScrollingIndicatorPosition();
2095             cancelScrollingIndicatorAnimations();
2096             if (immediately) {
2097                 mScrollIndicator.setVisibility(View.INVISIBLE);
2098                 mScrollIndicator.setAlpha(0f);
2099             } else {
2100                 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f);
2101                 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
2102                 mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
2103                     private boolean cancelled = false;
2104                     @Override
2105                     public void onAnimationCancel(android.animation.Animator animation) {
2106                         cancelled = true;
2107                     }
2108                     @Override
2109                     public void onAnimationEnd(Animator animation) {
2110                         if (!cancelled) {
2111                             mScrollIndicator.setVisibility(View.INVISIBLE);
2112                         }
2113                     }
2114                 });
2115                 mScrollIndicatorAnimator.start();
2116             }
2117         }
2118     }
2119 
2120     /**
2121      * To be overridden by subclasses to determine whether the scroll indicator should stretch to
2122      * fill its space on the track or not.
2123      */
hasElasticScrollIndicator()2124     protected boolean hasElasticScrollIndicator() {
2125         return true;
2126     }
2127 
updateScrollingIndicator()2128     private void updateScrollingIndicator() {
2129         if (getChildCount() <= 1) return;
2130         if (!isScrollingIndicatorEnabled()) return;
2131 
2132         getScrollingIndicator();
2133         if (mScrollIndicator != null) {
2134             updateScrollingIndicatorPosition();
2135         }
2136         if (mShouldShowScrollIndicator) {
2137             showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
2138         }
2139     }
2140 
updateScrollingIndicatorPosition()2141     private void updateScrollingIndicatorPosition() {
2142         if (!isScrollingIndicatorEnabled()) return;
2143         if (mScrollIndicator == null) return;
2144         int numPages = getChildCount();
2145         int pageWidth = getViewportWidth();
2146         int lastChildIndex = Math.max(0, getChildCount() - 1);
2147         int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
2148         int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
2149         int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
2150                 mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
2151 
2152         float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX));
2153         int indicatorSpace = trackWidth / numPages;
2154         int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
2155         if (hasElasticScrollIndicator()) {
2156             if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
2157                 mScrollIndicator.getLayoutParams().width = indicatorSpace;
2158                 mScrollIndicator.requestLayout();
2159             }
2160         } else {
2161             int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
2162             indicatorPos += indicatorCenterOffset;
2163         }
2164         mScrollIndicator.setTranslationX(indicatorPos);
2165     }
2166 
2167     // Animate the drag view back to the original position
animateDragViewToOriginalPosition()2168     void animateDragViewToOriginalPosition() {
2169         if (mDragView != null) {
2170             AnimatorSet anim = new AnimatorSet();
2171             anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
2172             anim.playTogether(
2173                     ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
2174                     ObjectAnimator.ofFloat(mDragView, "translationY", 0f));
2175             anim.addListener(new AnimatorListenerAdapter() {
2176                 @Override
2177                 public void onAnimationEnd(Animator animation) {
2178                     onPostReorderingAnimationCompleted();
2179                 }
2180             });
2181             anim.start();
2182         }
2183     }
2184 
2185     // "Zooms out" the PagedView to reveal more side pages
zoomOut()2186     protected boolean zoomOut() {
2187         if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2188             mZoomInOutAnim.cancel();
2189         }
2190 
2191         if (!(getScaleX() < 1f || getScaleY() < 1f)) {
2192             mZoomInOutAnim = new AnimatorSet();
2193             mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2194             mZoomInOutAnim.playTogether(
2195                     ObjectAnimator.ofFloat(this, "scaleX", mMinScale),
2196                     ObjectAnimator.ofFloat(this, "scaleY", mMinScale));
2197             mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2198                 @Override
2199                 public void onAnimationStart(Animator animation) {
2200                     // Show the delete drop target
2201                     if (mDeleteDropTarget != null) {
2202                         mDeleteDropTarget.setVisibility(View.VISIBLE);
2203                         mDeleteDropTarget.animate().alpha(1f)
2204                             .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2205                             .setListener(new AnimatorListenerAdapter() {
2206                                 @Override
2207                                 public void onAnimationStart(Animator animation) {
2208                                     mDeleteDropTarget.setAlpha(0f);
2209                                 }
2210                             });
2211                     }
2212                 }
2213             });
2214             mZoomInOutAnim.start();
2215             return true;
2216         }
2217         return false;
2218     }
2219 
onStartReordering()2220     protected void onStartReordering() {
2221         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2222             announceForAccessibility(mContext.getString(
2223                     R.string.keyguard_accessibility_widget_reorder_start));
2224         }
2225 
2226         // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
2227         setTouchState(TOUCH_STATE_REORDERING);
2228         mIsReordering = true;
2229 
2230         // Mark all the non-widget pages as invisible
2231         getVisiblePages(mTempVisiblePagesRange);
2232         boundByReorderablePages(true, mTempVisiblePagesRange);
2233         for (int i = 0; i < getPageCount(); ++i) {
2234             if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2235                 getPageAt(i).setAlpha(0f);
2236             }
2237         }
2238 
2239         // We must invalidate to trigger a redraw to update the layers such that the drag view
2240         // is always drawn on top
2241         invalidate();
2242     }
2243 
onPostReorderingAnimationCompleted()2244     private void onPostReorderingAnimationCompleted() {
2245         // Trigger the callback when reordering has settled
2246         --mPostReorderingPreZoomInRemainingAnimationCount;
2247         if (mPostReorderingPreZoomInRunnable != null &&
2248                 mPostReorderingPreZoomInRemainingAnimationCount == 0) {
2249             mPostReorderingPreZoomInRunnable.run();
2250             mPostReorderingPreZoomInRunnable = null;
2251         }
2252     }
2253 
onEndReordering()2254     protected void onEndReordering() {
2255         if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2256             if (mDeleteString != null) {
2257                 announceForAccessibility(mDeleteString);
2258                 mDeleteString = null;
2259             } else {
2260                 announceForAccessibility(mContext.getString(
2261                         R.string.keyguard_accessibility_widget_reorder_end));
2262             }
2263         }
2264         mIsReordering = false;
2265 
2266         // Mark all the non-widget pages as visible again
2267         getVisiblePages(mTempVisiblePagesRange);
2268         boundByReorderablePages(true, mTempVisiblePagesRange);
2269         for (int i = 0; i < getPageCount(); ++i) {
2270             if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2271                 getPageAt(i).setAlpha(1f);
2272             }
2273         }
2274     }
2275 
startReordering()2276     public boolean startReordering() {
2277         int dragViewIndex = getPageNearestToCenterOfScreen();
2278         mTempVisiblePagesRange[0] = 0;
2279         mTempVisiblePagesRange[1] = getPageCount() - 1;
2280         boundByReorderablePages(true, mTempVisiblePagesRange);
2281 
2282         // Check if we are within the reordering range
2283         if (mTempVisiblePagesRange[0] <= dragViewIndex &&
2284                 dragViewIndex <= mTempVisiblePagesRange[1]) {
2285             mReorderingStarted = true;
2286             if (zoomOut()) {
2287                 // Find the drag view under the pointer
2288                 mDragView = getChildAt(dragViewIndex);
2289 
2290                 onStartReordering();
2291             }
2292             return true;
2293         }
2294         return false;
2295     }
2296 
isReordering(boolean testTouchState)2297     boolean isReordering(boolean testTouchState) {
2298         boolean state = mIsReordering;
2299         if (testTouchState) {
2300             state &= (mTouchState == TOUCH_STATE_REORDERING);
2301         }
2302         return state;
2303     }
endReordering()2304     void endReordering() {
2305         // For simplicity, we call endReordering sometimes even if reordering was never started.
2306         // In that case, we don't want to do anything.
2307         if (!mReorderingStarted) return;
2308         mReorderingStarted = false;
2309 
2310         // If we haven't flung-to-delete the current child, then we just animate the drag view
2311         // back into position
2312         final Runnable onCompleteRunnable = new Runnable() {
2313             @Override
2314             public void run() {
2315                 onEndReordering();
2316             }
2317         };
2318         if (!mDeferringForDelete) {
2319             mPostReorderingPreZoomInRunnable = new Runnable() {
2320                 public void run() {
2321                     zoomIn(onCompleteRunnable);
2322                 };
2323             };
2324 
2325             mPostReorderingPreZoomInRemainingAnimationCount =
2326                     NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
2327             // Snap to the current page
2328             snapToPage(indexOfChild(mDragView), 0);
2329             // Animate the drag view back to the front position
2330             animateDragViewToOriginalPosition();
2331         } else {
2332             // Handled in post-delete-animation-callbacks
2333         }
2334     }
2335 
2336     // "Zooms in" the PagedView to highlight the current page
zoomIn(final Runnable onCompleteRunnable)2337     protected boolean zoomIn(final Runnable onCompleteRunnable) {
2338         if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2339             mZoomInOutAnim.cancel();
2340         }
2341         if (getScaleX() < 1f || getScaleY() < 1f) {
2342             mZoomInOutAnim = new AnimatorSet();
2343             mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2344             mZoomInOutAnim.playTogether(
2345                     ObjectAnimator.ofFloat(this, "scaleX", 1f),
2346                     ObjectAnimator.ofFloat(this, "scaleY", 1f));
2347             mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2348                 @Override
2349                 public void onAnimationStart(Animator animation) {
2350                     // Hide the delete drop target
2351                     if (mDeleteDropTarget != null) {
2352                         mDeleteDropTarget.animate().alpha(0f)
2353                             .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2354                             .setListener(new AnimatorListenerAdapter() {
2355                                 @Override
2356                                 public void onAnimationEnd(Animator animation) {
2357                                     mDeleteDropTarget.setVisibility(View.GONE);
2358                                 }
2359                             });
2360                     }
2361                 }
2362                 @Override
2363                 public void onAnimationCancel(Animator animation) {
2364                     mDragView = null;
2365                 }
2366                 @Override
2367                 public void onAnimationEnd(Animator animation) {
2368                     mDragView = null;
2369                     if (onCompleteRunnable != null) {
2370                         onCompleteRunnable.run();
2371                     }
2372                 }
2373             });
2374             mZoomInOutAnim.start();
2375             return true;
2376         } else {
2377             if (onCompleteRunnable != null) {
2378                 onCompleteRunnable.run();
2379             }
2380         }
2381         return false;
2382     }
2383 
2384     /*
2385      * Flinging to delete - IN PROGRESS
2386      */
isFlingingToDelete()2387     private PointF isFlingingToDelete() {
2388         ViewConfiguration config = ViewConfiguration.get(getContext());
2389         mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
2390 
2391         if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
2392             // Do a quick dot product test to ensure that we are flinging upwards
2393             PointF vel = new PointF(mVelocityTracker.getXVelocity(),
2394                     mVelocityTracker.getYVelocity());
2395             PointF upVec = new PointF(0f, -1f);
2396             float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
2397                     (vel.length() * upVec.length()));
2398             if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
2399                 return vel;
2400             }
2401         }
2402         return null;
2403     }
2404 
2405     /**
2406      * Creates an animation from the current drag view along its current velocity vector.
2407      * For this animation, the alpha runs for a fixed duration and we update the position
2408      * progressively.
2409      */
2410     private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
2411         private View mDragView;
2412         private PointF mVelocity;
2413         private Rect mFrom;
2414         private long mPrevTime;
2415         private float mFriction;
2416 
2417         private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
2418 
FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from, long startTime, float friction)2419         public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
2420                 long startTime, float friction) {
2421             mDragView = dragView;
2422             mVelocity = vel;
2423             mFrom = from;
2424             mPrevTime = startTime;
2425             mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
2426         }
2427 
2428         @Override
onAnimationUpdate(ValueAnimator animation)2429         public void onAnimationUpdate(ValueAnimator animation) {
2430             float t = ((Float) animation.getAnimatedValue()).floatValue();
2431             long curTime = AnimationUtils.currentAnimationTimeMillis();
2432 
2433             mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
2434             mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
2435 
2436             mDragView.setTranslationX(mFrom.left);
2437             mDragView.setTranslationY(mFrom.top);
2438             mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
2439 
2440             mVelocity.x *= mFriction;
2441             mVelocity.y *= mFriction;
2442             mPrevTime = curTime;
2443         }
2444     };
2445 
createPostDeleteAnimationRunnable(final View dragView)2446     private Runnable createPostDeleteAnimationRunnable(final View dragView) {
2447         return new Runnable() {
2448             @Override
2449             public void run() {
2450                 int dragViewIndex = indexOfChild(dragView);
2451 
2452                 // For each of the pages around the drag view, animate them from the previous
2453                 // position to the new position in the layout (as a result of the drag view moving
2454                 // in the layout)
2455                 // NOTE: We can make an assumption here because we have side-bound pages that we
2456                 //       will always have pages to animate in from the left
2457                 getVisiblePages(mTempVisiblePagesRange);
2458                 boundByReorderablePages(true, mTempVisiblePagesRange);
2459                 boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
2460                 boolean slideFromLeft = (isLastWidgetPage ||
2461                         dragViewIndex > mTempVisiblePagesRange[0]);
2462 
2463                 // Setup the scroll to the correct page before we swap the views
2464                 if (slideFromLeft) {
2465                     snapToPageImmediately(dragViewIndex - 1);
2466                 }
2467 
2468                 int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
2469                 int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
2470                 int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
2471                 int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
2472                 ArrayList<Animator> animations = new ArrayList<Animator>();
2473                 for (int i = lowerIndex; i <= upperIndex; ++i) {
2474                     View v = getChildAt(i);
2475                     // dragViewIndex < pageUnderPointIndex, so after we remove the
2476                     // drag view all subsequent views to pageUnderPointIndex will
2477                     // shift down.
2478                     int oldX = 0;
2479                     int newX = 0;
2480                     if (slideFromLeft) {
2481                         if (i == 0) {
2482                             // Simulate the page being offscreen with the page spacing
2483                             oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
2484                                     - mPageSpacing;
2485                         } else {
2486                             oldX = getViewportOffsetX() + getChildOffset(i - 1);
2487                         }
2488                         newX = getViewportOffsetX() + getChildOffset(i);
2489                     } else {
2490                         oldX = getChildOffset(i) - getChildOffset(i - 1);
2491                         newX = 0;
2492                     }
2493 
2494                     // Animate the view translation from its old position to its new
2495                     // position
2496                     AnimatorSet anim = (AnimatorSet) v.getTag();
2497                     if (anim != null) {
2498                         anim.cancel();
2499                     }
2500 
2501                     // Note: Hacky, but we want to skip any optimizations to not draw completely
2502                     // hidden views
2503                     v.setAlpha(Math.max(v.getAlpha(), 0.01f));
2504                     v.setTranslationX(oldX - newX);
2505                     anim = new AnimatorSet();
2506                     anim.playTogether(
2507                             ObjectAnimator.ofFloat(v, "translationX", 0f),
2508                             ObjectAnimator.ofFloat(v, "alpha", 1f));
2509                     animations.add(anim);
2510                     v.setTag(anim);
2511                 }
2512 
2513                 AnimatorSet slideAnimations = new AnimatorSet();
2514                 slideAnimations.playTogether(animations);
2515                 slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
2516                 slideAnimations.addListener(new AnimatorListenerAdapter() {
2517                     @Override
2518                     public void onAnimationEnd(Animator animation) {
2519                         final Runnable onCompleteRunnable = new Runnable() {
2520                             @Override
2521                             public void run() {
2522                                 mDeferringForDelete = false;
2523                                 onEndReordering();
2524                                 onRemoveViewAnimationCompleted();
2525                             }
2526                         };
2527                         zoomIn(onCompleteRunnable);
2528                     }
2529                 });
2530                 slideAnimations.start();
2531 
2532                 removeView(dragView);
2533                 onRemoveView(dragView, true);
2534             }
2535         };
2536     }
2537 
2538     public void onFlingToDelete(PointF vel) {
2539         final long startTime = AnimationUtils.currentAnimationTimeMillis();
2540 
2541         // NOTE: Because it takes time for the first frame of animation to actually be
2542         // called and we expect the animation to be a continuation of the fling, we have
2543         // to account for the time that has elapsed since the fling finished.  And since
2544         // we don't have a startDelay, we will always get call to update when we call
2545         // start() (which we want to ignore).
2546         final TimeInterpolator tInterpolator = new TimeInterpolator() {
2547             private int mCount = -1;
2548             private long mStartTime;
2549             private float mOffset;
2550             /* Anonymous inner class ctor */ {
2551                 mStartTime = startTime;
2552             }
2553 
2554             @Override
2555             public float getInterpolation(float t) {
2556                 if (mCount < 0) {
2557                     mCount++;
2558                 } else if (mCount == 0) {
2559                     mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
2560                             mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
2561                     mCount++;
2562                 }
2563                 return Math.min(1f, mOffset + t);
2564             }
2565         };
2566 
2567         final Rect from = new Rect();
2568         final View dragView = mDragView;
2569         from.left = (int) dragView.getTranslationX();
2570         from.top = (int) dragView.getTranslationY();
2571         AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
2572                 from, startTime, FLING_TO_DELETE_FRICTION);
2573 
2574         mDeleteString = getContext().getResources()
2575                 .getString(R.string.keyguard_accessibility_widget_deleted,
2576                         mDragView.getContentDescription());
2577         final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2578 
2579         // Create and start the animation
2580         ValueAnimator mDropAnim = new ValueAnimator();
2581         mDropAnim.setInterpolator(tInterpolator);
2582         mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
2583         mDropAnim.setFloatValues(0f, 1f);
2584         mDropAnim.addUpdateListener(updateCb);
2585         mDropAnim.addListener(new AnimatorListenerAdapter() {
2586             public void onAnimationEnd(Animator animation) {
2587                 onAnimationEndRunnable.run();
2588             }
2589         });
2590         mDropAnim.start();
2591         mDeferringForDelete = true;
2592     }
2593 
2594     /* Drag to delete */
2595     private boolean isHoveringOverDeleteDropTarget(int x, int y) {
2596         if (mDeleteDropTarget != null) {
2597             mAltTmpRect.set(0, 0, 0, 0);
2598             View parent = (View) mDeleteDropTarget.getParent();
2599             if (parent != null) {
2600                 parent.getGlobalVisibleRect(mAltTmpRect);
2601             }
2602             mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
2603             mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
2604             return mTmpRect.contains(x, y);
2605         }
2606         return false;
2607     }
2608 
2609     protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
2610 
2611     private void onDropToDelete() {
2612         final View dragView = mDragView;
2613 
2614         final float toScale = 0f;
2615         final float toAlpha = 0f;
2616 
2617         // Create and start the complex animation
2618         ArrayList<Animator> animations = new ArrayList<Animator>();
2619         AnimatorSet motionAnim = new AnimatorSet();
2620         motionAnim.setInterpolator(new DecelerateInterpolator(2));
2621         motionAnim.playTogether(
2622                 ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
2623                 ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
2624         animations.add(motionAnim);
2625 
2626         AnimatorSet alphaAnim = new AnimatorSet();
2627         alphaAnim.setInterpolator(new LinearInterpolator());
2628         alphaAnim.playTogether(
2629                 ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
2630         animations.add(alphaAnim);
2631 
2632         mDeleteString = getContext().getResources()
2633                 .getString(R.string.keyguard_accessibility_widget_deleted,
2634                         mDragView.getContentDescription());
2635         final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2636 
2637         AnimatorSet anim = new AnimatorSet();
2638         anim.playTogether(animations);
2639         anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
2640         anim.addListener(new AnimatorListenerAdapter() {
2641             public void onAnimationEnd(Animator animation) {
2642                 onAnimationEndRunnable.run();
2643             }
2644         });
2645         anim.start();
2646 
2647         mDeferringForDelete = true;
2648     }
2649 
2650     /* Accessibility */
2651     @Override
2652     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2653         super.onInitializeAccessibilityNodeInfo(info);
2654         info.setScrollable(getPageCount() > 1);
2655         if (getCurrentPage() < getPageCount() - 1) {
2656             info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2657         }
2658         if (getCurrentPage() > 0) {
2659             info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2660         }
2661     }
2662 
2663     @Override
2664     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2665         super.onInitializeAccessibilityEvent(event);
2666         event.setScrollable(true);
2667         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2668             event.setFromIndex(mCurrentPage);
2669             event.setToIndex(mCurrentPage);
2670             event.setItemCount(getChildCount());
2671         }
2672     }
2673 
2674     @Override
2675     public boolean performAccessibilityAction(int action, Bundle arguments) {
2676         if (super.performAccessibilityAction(action, arguments)) {
2677             return true;
2678         }
2679         switch (action) {
2680             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2681                 if (getCurrentPage() < getPageCount() - 1) {
2682                     scrollRight();
2683                     return true;
2684                 }
2685             } break;
2686             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2687                 if (getCurrentPage() > 0) {
2688                     scrollLeft();
2689                     return true;
2690                 }
2691             } break;
2692         }
2693         return false;
2694     }
2695 
2696     @Override
2697     public boolean onHoverEvent(android.view.MotionEvent event) {
2698         return true;
2699     }
2700 
2701     void beginCameraEvent() {
2702         mIsCameraEvent = true;
2703     }
2704 
2705     void endCameraEvent() {
2706         mIsCameraEvent = false;
2707     }
2708 
2709     AnimatorListenerAdapter mOnScreenAnimationListener = new AnimatorListenerAdapter() {
2710         @Override
2711         public void onAnimationEnd(Animator animation) {
2712             mWarpAnimation = null;
2713             if (mTouchState != TOUCH_STATE_SCROLLING && mTouchState != TOUCH_STATE_READY) {
2714                 animateWarpPageOffScreen("onScreen end", true);
2715             }
2716         }
2717 
2718         @Override
2719         public void onAnimationCancel(Animator animation) {
2720             super.onAnimationCancel(animation);
2721             mWarpAnimation = null;
2722         }
2723     };
2724 
2725     AnimatorListenerAdapter mOffScreenAnimationListener = new AnimatorListenerAdapter() {
2726         @Override
2727         public void onAnimationEnd(Animator animation) {
2728             mWarpAnimation = null;
2729             mWarpPageExposed = false;
2730             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
2731             v.setTranslationX(0.0f);
2732         }
2733 
2734         @Override
2735         public void onAnimationCancel(Animator animation) {
2736             super.onAnimationCancel(animation);
2737             mWarpAnimation = null;
2738         }
2739     };
2740 
2741     private void cancelWarpAnimation(String msg, boolean abortAnimation) {
2742         if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ",abort=" + abortAnimation + ")");
2743         if (abortAnimation) {
2744             // We're done with the animation and moving to a new page.  Let the scroller
2745             // take over the animation.
2746             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
2747             v.animate().cancel();
2748             // Make the scroll amount match the current warp position.
2749             scrollBy(Math.round(-v.getTranslationX()), 0);
2750             v.setTranslationX(0);
2751         } else {
2752             animateWarpPageOffScreen("canceled", true);
2753         }
2754     }
2755 
2756     private boolean isAnimatingWarpPage() {
2757         return mWarpAnimation != null;
2758     }
2759 
2760     private void animateWarpPageOnScreen(String reason) {
2761         if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")");
2762         if (!mWarpPageExposed) {
2763             mWarpPageExposed = true;
2764             dispatchOnPageBeginWarp();
2765             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
2766             if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX());
2767             DecelerateInterpolator interp = new DecelerateInterpolator(1.5f);
2768             int totalOffset = getCurrentWarpOffset();
2769             v.setTranslationX(totalOffset);
2770             mWarpAnimation = v.animate();
2771             mWarpAnimation.translationX(mWarpPeekAmount+totalOffset)
2772                     .setInterpolator(interp)
2773                     .setDuration(WARP_PEEK_ANIMATION_DURATION)
2774                     .setListener(mOnScreenAnimationListener);
2775         }
2776     }
2777 
2778     private int getCurrentWarpOffset() {
2779         if (mCurrentPage == getPageWarpIndex()) {
2780             return 0;
2781         }
2782         View viewRight = getPageAt(mCurrentPage + 1);
2783         View warpView = getPageAt(getPageWarpIndex());
2784         if (viewRight != warpView && viewRight != null && warpView != null) {
2785             return viewRight.getLeft() - warpView.getLeft();
2786         }
2787         return 0;
2788     }
2789 
2790     private void animateWarpPageOffScreen(String reason, boolean animate) {
2791         if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")");
2792         if (mWarpPageExposed) {
2793             dispatchOnPageEndWarp();
2794             KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
2795             if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX());
2796             AccelerateInterpolator interp = new AccelerateInterpolator(1.5f);
2797             int totalOffset = getCurrentWarpOffset();
2798             v.animate().translationX(totalOffset)
2799                     .setInterpolator(interp)
2800                     .setDuration(animate ? WARP_PEEK_ANIMATION_DURATION : 0)
2801                     .setListener(mOffScreenAnimationListener);
2802         } else {
2803             if (DEBUG_WARP) Log.e(TAG, "animateWarpPageOffScreen(): not warping", new Exception());
2804         }
2805     }
2806 
2807     /**
2808      * Swaps the position of the views by setting the left and right edges appropriately.
2809      */
2810     void swapPages(int indexA, int indexB) {
2811         View viewA = getPageAt(indexA);
2812         View viewB = getPageAt(indexB);
2813         if (viewA != viewB && viewA != null && viewB != null) {
2814             int deltaX = viewA.getLeft() - viewB.getLeft();
2815             viewA.offsetLeftAndRight(-deltaX);
2816             viewB.offsetLeftAndRight(deltaX);
2817         }
2818     }
2819 
2820     public void startPageWarp(int pageIndex) {
2821         if (DEBUG_WARP) Log.v(TAG, "START WARP");
2822         if (pageIndex != mCurrentPage + 1) {
2823             mPageSwapIndex = mCurrentPage + 1;
2824         }
2825         mPageWarpIndex = pageIndex;
2826     }
2827 
2828     protected int getPageWarpIndex() {
2829         return getPageCount() - 1;
2830     }
2831 
2832     public void stopPageWarp() {
2833         if (DEBUG_WARP) Log.v(TAG, "END WARP");
2834         // mPageSwapIndex is reset in snapToPage() after the scroll animation completes
2835     }
2836 
2837     public void onPageBeginWarp() {
2838 
2839     }
2840 
2841     public void onPageEndWarp() {
2842 
2843     }
2844 
2845 }
2846