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