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