Home | History | Annotate | Download | only in systemui
      1 /*
      2  * Copyright (C) 2011 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.systemui;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.ValueAnimator;
     23 import android.animation.ValueAnimator.AnimatorUpdateListener;
     24 import android.annotation.NonNull;
     25 import android.content.Context;
     26 import android.content.res.Resources;
     27 import android.graphics.RectF;
     28 import android.os.Handler;
     29 import android.util.ArrayMap;
     30 import android.util.Log;
     31 import android.view.MotionEvent;
     32 import android.view.VelocityTracker;
     33 import android.view.View;
     34 import android.view.ViewConfiguration;
     35 import android.view.accessibility.AccessibilityEvent;
     36 import com.android.systemui.classifier.FalsingManager;
     37 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
     38 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
     39 import com.android.systemui.statusbar.ExpandableNotificationRow;
     40 import com.android.systemui.statusbar.FlingAnimationUtils;
     41 
     42 public class SwipeHelper implements Gefingerpoken {
     43     static final String TAG = "com.android.systemui.SwipeHelper";
     44     private static final boolean DEBUG = false;
     45     private static final boolean DEBUG_INVALIDATE = false;
     46     private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
     47     private static final boolean CONSTRAIN_SWIPE = true;
     48     private static final boolean FADE_OUT_DURING_SWIPE = true;
     49     private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
     50 
     51     public static final int X = 0;
     52     public static final int Y = 1;
     53 
     54     private static final float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec
     55     private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
     56     private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
     57     private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
     58     private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
     59 
     60     static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
     61                                               // beyond which swipe progress->0
     62     public static final float SWIPED_FAR_ENOUGH_SIZE_FRACTION = 0.6f;
     63     static final float MAX_SCROLL_SIZE_FRACTION = 0.3f;
     64 
     65     private float mMinSwipeProgress = 0f;
     66     private float mMaxSwipeProgress = 1f;
     67 
     68     private final FlingAnimationUtils mFlingAnimationUtils;
     69     private float mPagingTouchSlop;
     70     private final Callback mCallback;
     71     private final Handler mHandler;
     72     private final int mSwipeDirection;
     73     private final VelocityTracker mVelocityTracker;
     74     private final FalsingManager mFalsingManager;
     75 
     76     private float mInitialTouchPos;
     77     private float mPerpendicularInitialTouchPos;
     78     private boolean mDragging;
     79     private boolean mSnappingChild;
     80     private View mCurrView;
     81     private boolean mCanCurrViewBeDimissed;
     82     private float mDensityScale;
     83     private float mTranslation = 0;
     84 
     85     private boolean mMenuRowIntercepting;
     86     private boolean mLongPressSent;
     87     private Runnable mWatchLongPress;
     88     private final long mLongPressTimeout;
     89 
     90     final private int[] mTmpPos = new int[2];
     91     private final int mFalsingThreshold;
     92     private boolean mTouchAboveFalsingThreshold;
     93     private boolean mDisableHwLayers;
     94     private final boolean mFadeDependingOnAmountSwiped;
     95     private final Context mContext;
     96 
     97     private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
     98 
     99     public SwipeHelper(int swipeDirection, Callback callback, Context context) {
    100         mContext = context;
    101         mCallback = callback;
    102         mHandler = new Handler();
    103         mSwipeDirection = swipeDirection;
    104         mVelocityTracker = VelocityTracker.obtain();
    105         mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
    106 
    107         // Extra long-press!
    108         mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f);
    109 
    110         Resources res = context.getResources();
    111         mDensityScale =  res.getDisplayMetrics().density;
    112         mFalsingThreshold = res.getDimensionPixelSize(R.dimen.swipe_helper_falsing_threshold);
    113         mFadeDependingOnAmountSwiped = res.getBoolean(R.bool.config_fadeDependingOnAmountSwiped);
    114         mFalsingManager = FalsingManager.getInstance(context);
    115         mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
    116     }
    117 
    118     public void setDensityScale(float densityScale) {
    119         mDensityScale = densityScale;
    120     }
    121 
    122     public void setPagingTouchSlop(float pagingTouchSlop) {
    123         mPagingTouchSlop = pagingTouchSlop;
    124     }
    125 
    126     public void setDisableHardwareLayers(boolean disableHwLayers) {
    127         mDisableHwLayers = disableHwLayers;
    128     }
    129 
    130     private float getPos(MotionEvent ev) {
    131         return mSwipeDirection == X ? ev.getX() : ev.getY();
    132     }
    133 
    134     private float getPerpendicularPos(MotionEvent ev) {
    135         return mSwipeDirection == X ? ev.getY() : ev.getX();
    136     }
    137 
    138     protected float getTranslation(View v) {
    139         return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
    140     }
    141 
    142     private float getVelocity(VelocityTracker vt) {
    143         return mSwipeDirection == X ? vt.getXVelocity() :
    144                 vt.getYVelocity();
    145     }
    146 
    147     protected ObjectAnimator createTranslationAnimation(View v, float newPos) {
    148         ObjectAnimator anim = ObjectAnimator.ofFloat(v,
    149                 mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
    150         return anim;
    151     }
    152 
    153     private float getPerpendicularVelocity(VelocityTracker vt) {
    154         return mSwipeDirection == X ? vt.getYVelocity() :
    155                 vt.getXVelocity();
    156     }
    157 
    158     protected Animator getViewTranslationAnimator(View v, float target,
    159             AnimatorUpdateListener listener) {
    160         ObjectAnimator anim = createTranslationAnimation(v, target);
    161         if (listener != null) {
    162             anim.addUpdateListener(listener);
    163         }
    164         return anim;
    165     }
    166 
    167     protected void setTranslation(View v, float translate) {
    168         if (v == null) {
    169             return;
    170         }
    171         if (mSwipeDirection == X) {
    172             v.setTranslationX(translate);
    173         } else {
    174             v.setTranslationY(translate);
    175         }
    176     }
    177 
    178     protected float getSize(View v) {
    179         return mSwipeDirection == X ? v.getMeasuredWidth() : v.getMeasuredHeight();
    180     }
    181 
    182     public void setMinSwipeProgress(float minSwipeProgress) {
    183         mMinSwipeProgress = minSwipeProgress;
    184     }
    185 
    186     public void setMaxSwipeProgress(float maxSwipeProgress) {
    187         mMaxSwipeProgress = maxSwipeProgress;
    188     }
    189 
    190     private float getSwipeProgressForOffset(View view, float translation) {
    191         float viewSize = getSize(view);
    192         float result = Math.abs(translation / viewSize);
    193         return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
    194     }
    195 
    196     private float getSwipeAlpha(float progress) {
    197         if (mFadeDependingOnAmountSwiped) {
    198             // The more progress has been fade, the lower the alpha value so that the view fades.
    199             return Math.max(1 - progress, 0);
    200         }
    201 
    202         return 1f - Math.max(0, Math.min(1, progress / SWIPE_PROGRESS_FADE_END));
    203     }
    204 
    205     private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
    206         updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView));
    207     }
    208 
    209     private void updateSwipeProgressFromOffset(View animView, boolean dismissable,
    210             float translation) {
    211         float swipeProgress = getSwipeProgressForOffset(animView, translation);
    212         if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
    213             if (FADE_OUT_DURING_SWIPE && dismissable) {
    214                 if (!mDisableHwLayers) {
    215                     if (swipeProgress != 0f && swipeProgress != 1f) {
    216                         animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    217                     } else {
    218                         animView.setLayerType(View.LAYER_TYPE_NONE, null);
    219                     }
    220                 }
    221                 animView.setAlpha(getSwipeAlpha(swipeProgress));
    222             }
    223         }
    224         invalidateGlobalRegion(animView);
    225     }
    226 
    227     // invalidate the view's own bounds all the way up the view hierarchy
    228     public static void invalidateGlobalRegion(View view) {
    229         invalidateGlobalRegion(
    230             view,
    231             new RectF(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
    232     }
    233 
    234     // invalidate a rectangle relative to the view's coordinate system all the way up the view
    235     // hierarchy
    236     public static void invalidateGlobalRegion(View view, RectF childBounds) {
    237         //childBounds.offset(view.getTranslationX(), view.getTranslationY());
    238         if (DEBUG_INVALIDATE)
    239             Log.v(TAG, "-------------");
    240         while (view.getParent() != null && view.getParent() instanceof View) {
    241             view = (View) view.getParent();
    242             view.getMatrix().mapRect(childBounds);
    243             view.invalidate((int) Math.floor(childBounds.left),
    244                             (int) Math.floor(childBounds.top),
    245                             (int) Math.ceil(childBounds.right),
    246                             (int) Math.ceil(childBounds.bottom));
    247             if (DEBUG_INVALIDATE) {
    248                 Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
    249                         + "," + (int) Math.floor(childBounds.top)
    250                         + "," + (int) Math.ceil(childBounds.right)
    251                         + "," + (int) Math.ceil(childBounds.bottom));
    252             }
    253         }
    254     }
    255 
    256     public void cancelLongPress() {
    257         if (mWatchLongPress != null) {
    258             mHandler.removeCallbacks(mWatchLongPress);
    259             mWatchLongPress = null;
    260         }
    261     }
    262 
    263     @Override
    264     public boolean onInterceptTouchEvent(final MotionEvent ev) {
    265         if (mCurrView instanceof ExpandableNotificationRow) {
    266             NotificationMenuRowPlugin nmr = ((ExpandableNotificationRow) mCurrView).getProvider();
    267             mMenuRowIntercepting = nmr.onInterceptTouchEvent(mCurrView, ev);
    268         }
    269         final int action = ev.getAction();
    270 
    271         switch (action) {
    272             case MotionEvent.ACTION_DOWN:
    273                 mTouchAboveFalsingThreshold = false;
    274                 mDragging = false;
    275                 mSnappingChild = false;
    276                 mLongPressSent = false;
    277                 mVelocityTracker.clear();
    278                 mCurrView = mCallback.getChildAtPosition(ev);
    279 
    280                 if (mCurrView != null) {
    281                     onDownUpdate(mCurrView, ev);
    282                     mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
    283                     mVelocityTracker.addMovement(ev);
    284                     mInitialTouchPos = getPos(ev);
    285                     mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
    286                     mTranslation = getTranslation(mCurrView);
    287                     if (mWatchLongPress == null) {
    288                         mWatchLongPress = new Runnable() {
    289                             @Override
    290                             public void run() {
    291                                 if (mCurrView != null && !mLongPressSent) {
    292                                     mLongPressSent = true;
    293                                     mCurrView.getLocationOnScreen(mTmpPos);
    294                                     final int x = (int) ev.getRawX() - mTmpPos[0];
    295                                     final int y = (int) ev.getRawY() - mTmpPos[1];
    296                                     if (mCurrView instanceof ExpandableNotificationRow) {
    297                                         mCurrView.sendAccessibilityEvent(
    298                                                 AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
    299                                         ExpandableNotificationRow currRow =
    300                                                 (ExpandableNotificationRow) mCurrView;
    301                                         currRow.doLongClickCallback(x, y);
    302                                     }
    303                                 }
    304                             }
    305                         };
    306                     }
    307                     mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
    308                 }
    309                 break;
    310 
    311             case MotionEvent.ACTION_MOVE:
    312                 if (mCurrView != null && !mLongPressSent) {
    313                     mVelocityTracker.addMovement(ev);
    314                     float pos = getPos(ev);
    315                     float perpendicularPos = getPerpendicularPos(ev);
    316                     float delta = pos - mInitialTouchPos;
    317                     float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos;
    318                     if (Math.abs(delta) > mPagingTouchSlop
    319                             && Math.abs(delta) > Math.abs(deltaPerpendicular)) {
    320                         if (mCallback.canChildBeDragged(mCurrView)) {
    321                             mCallback.onBeginDrag(mCurrView);
    322                             mDragging = true;
    323                             mInitialTouchPos = getPos(ev);
    324                             mTranslation = getTranslation(mCurrView);
    325                         }
    326                         cancelLongPress();
    327                     }
    328                 }
    329                 break;
    330 
    331             case MotionEvent.ACTION_UP:
    332             case MotionEvent.ACTION_CANCEL:
    333                 final boolean captured = (mDragging || mLongPressSent || mMenuRowIntercepting);
    334                 mDragging = false;
    335                 mCurrView = null;
    336                 mLongPressSent = false;
    337                 mMenuRowIntercepting = false;
    338                 cancelLongPress();
    339                 if (captured) return true;
    340                 break;
    341         }
    342         return mDragging || mLongPressSent || mMenuRowIntercepting;
    343     }
    344 
    345     /**
    346      * @param view The view to be dismissed
    347      * @param velocity The desired pixels/second speed at which the view should move
    348      * @param useAccelerateInterpolator Should an accelerating Interpolator be used
    349      */
    350     public void dismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
    351         dismissChild(view, velocity, null /* endAction */, 0 /* delay */,
    352                 useAccelerateInterpolator, 0 /* fixedDuration */, false /* isDismissAll */);
    353     }
    354 
    355     /**
    356      * @param view The view to be dismissed
    357      * @param velocity The desired pixels/second speed at which the view should move
    358      * @param endAction The action to perform at the end
    359      * @param delay The delay after which we should start
    360      * @param useAccelerateInterpolator Should an accelerating Interpolator be used
    361      * @param fixedDuration If not 0, this exact duration will be taken
    362      */
    363     public void dismissChild(final View animView, float velocity, final Runnable endAction,
    364             long delay, boolean useAccelerateInterpolator, long fixedDuration,
    365             boolean isDismissAll) {
    366         final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
    367         float newPos;
    368         boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
    369 
    370         // if we use the Menu to dismiss an item in landscape, animate up
    371         boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
    372                 && mSwipeDirection == Y;
    373         // if the language is rtl we prefer swiping to the left
    374         boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
    375                 && isLayoutRtl;
    376         boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) ||
    377                 (getTranslation(animView) < 0 && !isDismissAll);
    378         if (animateLeft || animateLeftForRtl || animateUpForMenu) {
    379             newPos = -getSize(animView);
    380         } else {
    381             newPos = getSize(animView);
    382         }
    383         long duration;
    384         if (fixedDuration == 0) {
    385             duration = MAX_ESCAPE_ANIMATION_DURATION;
    386             if (velocity != 0) {
    387                 duration = Math.min(duration,
    388                         (int) (Math.abs(newPos - getTranslation(animView)) * 1000f / Math
    389                                 .abs(velocity))
    390                 );
    391             } else {
    392                 duration = DEFAULT_ESCAPE_ANIMATION_DURATION;
    393             }
    394         } else {
    395             duration = fixedDuration;
    396         }
    397 
    398         if (!mDisableHwLayers) {
    399             animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    400         }
    401         AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
    402             @Override
    403             public void onAnimationUpdate(ValueAnimator animation) {
    404                 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
    405             }
    406         };
    407 
    408         Animator anim = getViewTranslationAnimator(animView, newPos, updateListener);
    409         if (anim == null) {
    410             return;
    411         }
    412         if (useAccelerateInterpolator) {
    413             anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
    414             anim.setDuration(duration);
    415         } else {
    416             mFlingAnimationUtils.applyDismissing(anim, getTranslation(animView),
    417                     newPos, velocity, getSize(animView));
    418         }
    419         if (delay > 0) {
    420             anim.setStartDelay(delay);
    421         }
    422         anim.addListener(new AnimatorListenerAdapter() {
    423             private boolean mCancelled;
    424 
    425             @Override
    426             public void onAnimationCancel(Animator animation) {
    427                 mCancelled = true;
    428             }
    429 
    430             @Override
    431             public void onAnimationEnd(Animator animation) {
    432                 updateSwipeProgressFromOffset(animView, canBeDismissed);
    433                 mDismissPendingMap.remove(animView);
    434                 boolean wasRemoved = false;
    435                 if (animView instanceof ExpandableNotificationRow) {
    436                     ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
    437                     wasRemoved = row.isRemoved();
    438                 }
    439                 if (!mCancelled || wasRemoved) {
    440                     mCallback.onChildDismissed(animView);
    441                 }
    442                 if (endAction != null) {
    443                     endAction.run();
    444                 }
    445                 if (!mDisableHwLayers) {
    446                     animView.setLayerType(View.LAYER_TYPE_NONE, null);
    447                 }
    448             }
    449         });
    450 
    451         prepareDismissAnimation(animView, anim);
    452         mDismissPendingMap.put(animView, anim);
    453         anim.start();
    454     }
    455 
    456     /**
    457      * Called to update the dismiss animation.
    458      */
    459     protected void prepareDismissAnimation(View view, Animator anim) {
    460         // Do nothing
    461     }
    462 
    463     public void snapChild(final View animView, final float targetLeft, float velocity) {
    464         final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
    465         AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
    466             @Override
    467             public void onAnimationUpdate(ValueAnimator animation) {
    468                 onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
    469             }
    470         };
    471 
    472         Animator anim = getViewTranslationAnimator(animView, targetLeft, updateListener);
    473         if (anim == null) {
    474             return;
    475         }
    476         int duration = SNAP_ANIM_LEN;
    477         anim.setDuration(duration);
    478         anim.addListener(new AnimatorListenerAdapter() {
    479             boolean wasCancelled = false;
    480 
    481             @Override
    482             public void onAnimationCancel(Animator animator) {
    483                 wasCancelled = true;
    484             }
    485 
    486             @Override
    487             public void onAnimationEnd(Animator animator) {
    488                 mSnappingChild = false;
    489                 if (!wasCancelled) {
    490                     updateSwipeProgressFromOffset(animView, canBeDismissed);
    491                     mCallback.onChildSnappedBack(animView, targetLeft);
    492                 }
    493             }
    494         });
    495         prepareSnapBackAnimation(animView, anim);
    496         mSnappingChild = true;
    497         anim.start();
    498     }
    499 
    500     /**
    501      * Called to update the snap back animation.
    502      */
    503     protected void prepareSnapBackAnimation(View view, Animator anim) {
    504         // Do nothing
    505     }
    506 
    507     /**
    508      * Called when there's a down event.
    509      */
    510     public void onDownUpdate(View currView, MotionEvent ev) {
    511         // Do nothing
    512     }
    513 
    514     /**
    515      * Called on a move event.
    516      */
    517     protected void onMoveUpdate(View view, MotionEvent ev, float totalTranslation, float delta) {
    518         // Do nothing
    519     }
    520 
    521     /**
    522      * Called in {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} when the current
    523      * view is being animated to dismiss or snap.
    524      */
    525     public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
    526         updateSwipeProgressFromOffset(animView, canBeDismissed, value);
    527     }
    528 
    529     private void snapChildInstantly(final View view) {
    530         final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view);
    531         setTranslation(view, 0);
    532         updateSwipeProgressFromOffset(view, canAnimViewBeDismissed);
    533     }
    534 
    535     /**
    536      * Called when a view is updated to be non-dismissable, if the view was being dismissed before
    537      * the update this will handle snapping it back into place.
    538      *
    539      * @param view the view to snap if necessary.
    540      * @param animate whether to animate the snap or not.
    541      * @param targetLeft the target to snap to.
    542      */
    543     public void snapChildIfNeeded(final View view, boolean animate, float targetLeft) {
    544         if ((mDragging && mCurrView == view) || mSnappingChild) {
    545             return;
    546         }
    547         boolean needToSnap = false;
    548         Animator dismissPendingAnim = mDismissPendingMap.get(view);
    549         if (dismissPendingAnim != null) {
    550             needToSnap = true;
    551             dismissPendingAnim.cancel();
    552         } else if (getTranslation(view) != 0) {
    553             needToSnap = true;
    554         }
    555         if (needToSnap) {
    556             if (animate) {
    557                 snapChild(view, targetLeft, 0.0f /* velocity */);
    558             } else {
    559                 snapChildInstantly(view);
    560             }
    561         }
    562     }
    563 
    564     @Override
    565     public boolean onTouchEvent(MotionEvent ev) {
    566         if (mLongPressSent && !mMenuRowIntercepting) {
    567             return true;
    568         }
    569 
    570         if (!mDragging && !mMenuRowIntercepting) {
    571             if (mCallback.getChildAtPosition(ev) != null) {
    572 
    573                 // We are dragging directly over a card, make sure that we also catch the gesture
    574                 // even if nobody else wants the touch event.
    575                 onInterceptTouchEvent(ev);
    576                 return true;
    577             } else {
    578 
    579                 // We are not doing anything, make sure the long press callback
    580                 // is not still ticking like a bomb waiting to go off.
    581                 cancelLongPress();
    582                 return false;
    583             }
    584         }
    585 
    586         mVelocityTracker.addMovement(ev);
    587         final int action = ev.getAction();
    588         switch (action) {
    589             case MotionEvent.ACTION_OUTSIDE:
    590             case MotionEvent.ACTION_MOVE:
    591                 if (mCurrView != null) {
    592                     float delta = getPos(ev) - mInitialTouchPos;
    593                     float absDelta = Math.abs(delta);
    594                     if (absDelta >= getFalsingThreshold()) {
    595                         mTouchAboveFalsingThreshold = true;
    596                     }
    597                     // don't let items that can't be dismissed be dragged more than
    598                     // maxScrollDistance
    599                     if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
    600                         float size = getSize(mCurrView);
    601                         float maxScrollDistance = MAX_SCROLL_SIZE_FRACTION * size;
    602                         if (absDelta >= size) {
    603                             delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
    604                         } else {
    605                             delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
    606                         }
    607                     }
    608 
    609                     setTranslation(mCurrView, mTranslation + delta);
    610                     updateSwipeProgressFromOffset(mCurrView, mCanCurrViewBeDimissed);
    611                     onMoveUpdate(mCurrView, ev, mTranslation + delta, delta);
    612                 }
    613                 break;
    614             case MotionEvent.ACTION_UP:
    615             case MotionEvent.ACTION_CANCEL:
    616                 if (mCurrView == null) {
    617                     break;
    618                 }
    619                 mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, getMaxVelocity());
    620                 float velocity = getVelocity(mVelocityTracker);
    621 
    622                 if (!handleUpEvent(ev, mCurrView, velocity, getTranslation(mCurrView))) {
    623                     if (isDismissGesture(ev)) {
    624                         // flingadingy
    625                         dismissChild(mCurrView, velocity,
    626                                 !swipedFastEnough() /* useAccelerateInterpolator */);
    627                     } else {
    628                         // snappity
    629                         mCallback.onDragCancelled(mCurrView);
    630                         snapChild(mCurrView, 0 /* leftTarget */, velocity);
    631                     }
    632                     mCurrView = null;
    633                 }
    634                 mDragging = false;
    635                 break;
    636         }
    637         return true;
    638     }
    639 
    640     private int getFalsingThreshold() {
    641         float factor = mCallback.getFalsingThresholdFactor();
    642         return (int) (mFalsingThreshold * factor);
    643     }
    644 
    645     private float getMaxVelocity() {
    646         return MAX_DISMISS_VELOCITY * mDensityScale;
    647     }
    648 
    649     protected float getEscapeVelocity() {
    650         return getUnscaledEscapeVelocity() * mDensityScale;
    651     }
    652 
    653     protected float getUnscaledEscapeVelocity() {
    654         return SWIPE_ESCAPE_VELOCITY;
    655     }
    656 
    657     protected long getMaxEscapeAnimDuration() {
    658         return MAX_ESCAPE_ANIMATION_DURATION;
    659     }
    660 
    661     protected boolean swipedFarEnough() {
    662         float translation = getTranslation(mCurrView);
    663         return DISMISS_IF_SWIPED_FAR_ENOUGH
    664                 && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(mCurrView);
    665     }
    666 
    667     public boolean isDismissGesture(MotionEvent ev) {
    668         return ev.getActionMasked() == MotionEvent.ACTION_UP
    669                 && !isFalseGesture(ev) && (swipedFastEnough() || swipedFarEnough())
    670                 && mCallback.canChildBeDismissed(mCurrView);
    671     }
    672 
    673     public boolean isFalseGesture(MotionEvent ev) {
    674         boolean falsingDetected = mCallback.isAntiFalsingNeeded();
    675         if (mFalsingManager.isClassiferEnabled()) {
    676             falsingDetected = falsingDetected && mFalsingManager.isFalseTouch();
    677         } else {
    678             falsingDetected = falsingDetected && !mTouchAboveFalsingThreshold;
    679         }
    680         return falsingDetected;
    681     }
    682 
    683     protected boolean swipedFastEnough() {
    684         float velocity = getVelocity(mVelocityTracker);
    685         float translation = getTranslation(mCurrView);
    686         boolean ret = (Math.abs(velocity) > getEscapeVelocity())
    687                 && (velocity > 0) == (translation > 0);
    688         return ret;
    689     }
    690 
    691     protected boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
    692             float translation) {
    693         return false;
    694     }
    695 
    696     public interface Callback {
    697         View getChildAtPosition(MotionEvent ev);
    698 
    699         boolean canChildBeDismissed(View v);
    700 
    701         boolean isAntiFalsingNeeded();
    702 
    703         void onBeginDrag(View v);
    704 
    705         void onChildDismissed(View v);
    706 
    707         void onDragCancelled(View v);
    708 
    709         /**
    710          * Called when the child is snapped to a position.
    711          *
    712          * @param animView the view that was snapped.
    713          * @param targetLeft the left position the view was snapped to.
    714          */
    715         void onChildSnappedBack(View animView, float targetLeft);
    716 
    717         /**
    718          * Updates the swipe progress on a child.
    719          *
    720          * @return if true, prevents the default alpha fading.
    721          */
    722         boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
    723 
    724         /**
    725          * @return The factor the falsing threshold should be multiplied with
    726          */
    727         float getFalsingThresholdFactor();
    728 
    729         /**
    730          * @return If true, the given view is draggable.
    731          */
    732         default boolean canChildBeDragged(@NonNull View animView) { return true; }
    733     }
    734 }
    735