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 SwipeHelper(int swipeDirection, Callback callback, Context context)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 setLongPressListener(LongPressListener listener)106 public void setLongPressListener(LongPressListener listener) { 107 mLongPressListener = listener; 108 } 109 setDensityScale(float densityScale)110 public void setDensityScale(float densityScale) { 111 mDensityScale = densityScale; 112 } 113 setPagingTouchSlop(float pagingTouchSlop)114 public void setPagingTouchSlop(float pagingTouchSlop) { 115 mPagingTouchSlop = pagingTouchSlop; 116 } 117 setDisableHardwareLayers(boolean disableHwLayers)118 public void setDisableHardwareLayers(boolean disableHwLayers) { 119 mDisableHwLayers = disableHwLayers; 120 } 121 getPos(MotionEvent ev)122 private float getPos(MotionEvent ev) { 123 return mSwipeDirection == X ? ev.getX() : ev.getY(); 124 } 125 getPerpendicularPos(MotionEvent ev)126 private float getPerpendicularPos(MotionEvent ev) { 127 return mSwipeDirection == X ? ev.getY() : ev.getX(); 128 } 129 getTranslation(View v)130 protected float getTranslation(View v) { 131 return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY(); 132 } 133 getVelocity(VelocityTracker vt)134 private float getVelocity(VelocityTracker vt) { 135 return mSwipeDirection == X ? vt.getXVelocity() : 136 vt.getYVelocity(); 137 } 138 createTranslationAnimation(View v, float newPos)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 getPerpendicularVelocity(VelocityTracker vt)145 private float getPerpendicularVelocity(VelocityTracker vt) { 146 return mSwipeDirection == X ? vt.getYVelocity() : 147 vt.getXVelocity(); 148 } 149 getViewTranslationAnimator(View v, float target, AnimatorUpdateListener listener)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 setTranslation(View v, float translate)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 getSize(View v)170 protected float getSize(View v) { 171 return mSwipeDirection == X ? v.getMeasuredWidth() : 172 v.getMeasuredHeight(); 173 } 174 setMinSwipeProgress(float minSwipeProgress)175 public void setMinSwipeProgress(float minSwipeProgress) { 176 mMinSwipeProgress = minSwipeProgress; 177 } 178 setMaxSwipeProgress(float maxSwipeProgress)179 public void setMaxSwipeProgress(float maxSwipeProgress) { 180 mMaxSwipeProgress = maxSwipeProgress; 181 } 182 getSwipeProgressForOffset(View view, float translation)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 getSwipeAlpha(float progress)189 private float getSwipeAlpha(float progress) { 190 return Math.min(0, Math.max(1, progress / SWIPE_PROGRESS_FADE_END)); 191 } 192 updateSwipeProgressFromOffset(View animView, boolean dismissable)193 private void updateSwipeProgressFromOffset(View animView, boolean dismissable) { 194 updateSwipeProgressFromOffset(animView, dismissable, getTranslation(animView)); 195 } 196 updateSwipeProgressFromOffset(View animView, boolean dismissable, float translation)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 invalidateGlobalRegion(View view)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 invalidateGlobalRegion(View view, RectF childBounds)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 removeLongPressCallback()245 public void removeLongPressCallback() { 246 if (mWatchLongPress != null) { 247 mHandler.removeCallbacks(mWatchLongPress); 248 mWatchLongPress = null; 249 } 250 } 251 onInterceptTouchEvent(final MotionEvent ev)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 */ dismissChild(final View view, float velocity, boolean useAccelerateInterpolator)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 */ dismissChild(final View animView, float velocity, final Runnable endAction, long delay, boolean useAccelerateInterpolator, long fixedDuration, boolean isDismissAll)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 dismissChild(mCurrView, velocity, !swipedFastEnough() )585 dismissChild(mCurrView, velocity, 586 !swipedFastEnough() /* useAccelerateInterpolator */); 587 } else { 588 // snappity 589 mCallback.onDragCancelled(mCurrView); snapChild(mCurrView, 0 , velocity)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