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