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