• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.statusbar.phone;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.view.MotionEvent;
24 import android.view.VelocityTracker;
25 import android.view.View;
26 import android.view.ViewConfiguration;
27 
28 import com.android.systemui.Interpolators;
29 import com.android.systemui.R;
30 import com.android.systemui.classifier.FalsingManagerFactory;
31 import com.android.systemui.plugins.FalsingManager;
32 import com.android.systemui.statusbar.FlingAnimationUtils;
33 import com.android.systemui.statusbar.KeyguardAffordanceView;
34 
35 /**
36  * A touch handler of the keyguard which is responsible for launching phone and camera affordances.
37  */
38 public class KeyguardAffordanceHelper {
39 
40     public static final long HINT_PHASE1_DURATION = 200;
41     private static final long HINT_PHASE2_DURATION = 350;
42     private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f;
43     private static final int HINT_CIRCLE_OPEN_DURATION = 500;
44 
45     private final Context mContext;
46     private final Callback mCallback;
47 
48     private FlingAnimationUtils mFlingAnimationUtils;
49     private VelocityTracker mVelocityTracker;
50     private boolean mSwipingInProgress;
51     private float mInitialTouchX;
52     private float mInitialTouchY;
53     private float mTranslation;
54     private float mTranslationOnDown;
55     private int mTouchSlop;
56     private int mMinTranslationAmount;
57     private int mMinFlingVelocity;
58     private int mHintGrowAmount;
59     private KeyguardAffordanceView mLeftIcon;
60     private KeyguardAffordanceView mRightIcon;
61     private Animator mSwipeAnimator;
62     private FalsingManager mFalsingManager;
63     private int mMinBackgroundRadius;
64     private boolean mMotionCancelled;
65     private int mTouchTargetSize;
66     private View mTargetedView;
67     private boolean mTouchSlopExeeded;
68     private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
69         @Override
70         public void onAnimationEnd(Animator animation) {
71             mSwipeAnimator = null;
72             mSwipingInProgress = false;
73             mTargetedView = null;
74         }
75     };
76     private Runnable mAnimationEndRunnable = new Runnable() {
77         @Override
78         public void run() {
79             mCallback.onAnimationToSideEnded();
80         }
81     };
82 
KeyguardAffordanceHelper(Callback callback, Context context)83     KeyguardAffordanceHelper(Callback callback, Context context) {
84         mContext = context;
85         mCallback = callback;
86         initIcons();
87         updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
88         updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
89         initDimens();
90     }
91 
initDimens()92     private void initDimens() {
93         final ViewConfiguration configuration = ViewConfiguration.get(mContext);
94         mTouchSlop = configuration.getScaledPagingTouchSlop();
95         mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
96         mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
97                 R.dimen.keyguard_min_swipe_amount);
98         mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
99                 R.dimen.keyguard_affordance_min_background_radius);
100         mTouchTargetSize = mContext.getResources().getDimensionPixelSize(
101                 R.dimen.keyguard_affordance_touch_target_size);
102         mHintGrowAmount =
103                 mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
104         mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
105         mFalsingManager = FalsingManagerFactory.getInstance(mContext);
106     }
107 
initIcons()108     private void initIcons() {
109         mLeftIcon = mCallback.getLeftIcon();
110         mRightIcon = mCallback.getRightIcon();
111         updatePreviews();
112     }
113 
updatePreviews()114     public void updatePreviews() {
115         mLeftIcon.setPreviewView(mCallback.getLeftPreview());
116         mRightIcon.setPreviewView(mCallback.getRightPreview());
117     }
118 
onTouchEvent(MotionEvent event)119     public boolean onTouchEvent(MotionEvent event) {
120         int action = event.getActionMasked();
121         if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
122             return false;
123         }
124         final float y = event.getY();
125         final float x = event.getX();
126 
127         boolean isUp = false;
128         switch (action) {
129             case MotionEvent.ACTION_DOWN:
130                 View targetView = getIconAtPosition(x, y);
131                 if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
132                     mMotionCancelled = true;
133                     return false;
134                 }
135                 if (mTargetedView != null) {
136                     cancelAnimation();
137                 } else {
138                     mTouchSlopExeeded = false;
139                 }
140                 startSwiping(targetView);
141                 mInitialTouchX = x;
142                 mInitialTouchY = y;
143                 mTranslationOnDown = mTranslation;
144                 initVelocityTracker();
145                 trackMovement(event);
146                 mMotionCancelled = false;
147                 break;
148             case MotionEvent.ACTION_POINTER_DOWN:
149                 mMotionCancelled = true;
150                 endMotion(true /* forceSnapBack */, x, y);
151                 break;
152             case MotionEvent.ACTION_MOVE:
153                 trackMovement(event);
154                 float xDist = x - mInitialTouchX;
155                 float yDist = y - mInitialTouchY;
156                 float distance = (float) Math.hypot(xDist, yDist);
157                 if (!mTouchSlopExeeded && distance > mTouchSlop) {
158                     mTouchSlopExeeded = true;
159                 }
160                 if (mSwipingInProgress) {
161                     if (mTargetedView == mRightIcon) {
162                         distance = mTranslationOnDown - distance;
163                         distance = Math.min(0, distance);
164                     } else {
165                         distance = mTranslationOnDown + distance;
166                         distance = Math.max(0, distance);
167                     }
168                     setTranslation(distance, false /* isReset */, false /* animateReset */);
169                 }
170                 break;
171 
172             case MotionEvent.ACTION_UP:
173                 isUp = true;
174             case MotionEvent.ACTION_CANCEL:
175                 boolean hintOnTheRight = mTargetedView == mRightIcon;
176                 trackMovement(event);
177                 endMotion(!isUp, x, y);
178                 if (!mTouchSlopExeeded && isUp) {
179                     mCallback.onIconClicked(hintOnTheRight);
180                 }
181                 break;
182         }
183         return true;
184     }
185 
startSwiping(View targetView)186     private void startSwiping(View targetView) {
187         mCallback.onSwipingStarted(targetView == mRightIcon);
188         mSwipingInProgress = true;
189         mTargetedView = targetView;
190     }
191 
getIconAtPosition(float x, float y)192     private View getIconAtPosition(float x, float y) {
193         if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
194             return mLeftIcon;
195         }
196         if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
197             return mRightIcon;
198         }
199         return null;
200     }
201 
isOnAffordanceIcon(float x, float y)202     public boolean isOnAffordanceIcon(float x, float y) {
203         return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
204     }
205 
isOnIcon(View icon, float x, float y)206     private boolean isOnIcon(View icon, float x, float y) {
207         float iconX = icon.getX() + icon.getWidth() / 2.0f;
208         float iconY = icon.getY() + icon.getHeight() / 2.0f;
209         double distance = Math.hypot(x - iconX, y - iconY);
210         return distance <= mTouchTargetSize / 2;
211     }
212 
endMotion(boolean forceSnapBack, float lastX, float lastY)213     private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
214         if (mSwipingInProgress) {
215             flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
216         } else {
217             mTargetedView = null;
218         }
219         if (mVelocityTracker != null) {
220             mVelocityTracker.recycle();
221             mVelocityTracker = null;
222         }
223     }
224 
rightSwipePossible()225     private boolean rightSwipePossible() {
226         return mRightIcon.getVisibility() == View.VISIBLE;
227     }
228 
leftSwipePossible()229     private boolean leftSwipePossible() {
230         return mLeftIcon.getVisibility() == View.VISIBLE;
231     }
232 
onInterceptTouchEvent(MotionEvent ev)233     public boolean onInterceptTouchEvent(MotionEvent ev) {
234         return false;
235     }
236 
startHintAnimation(boolean right, Runnable onFinishedListener)237     public void startHintAnimation(boolean right,
238             Runnable onFinishedListener) {
239         cancelAnimation();
240         startHintAnimationPhase1(right, onFinishedListener);
241     }
242 
startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener)243     private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
244         final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
245         ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
246         animator.addListener(new AnimatorListenerAdapter() {
247             private boolean mCancelled;
248 
249             @Override
250             public void onAnimationCancel(Animator animation) {
251                 mCancelled = true;
252             }
253 
254             @Override
255             public void onAnimationEnd(Animator animation) {
256                 if (mCancelled) {
257                     mSwipeAnimator = null;
258                     mTargetedView = null;
259                     onFinishedListener.run();
260                 } else {
261                     startUnlockHintAnimationPhase2(right, onFinishedListener);
262                 }
263             }
264         });
265         animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
266         animator.setDuration(HINT_PHASE1_DURATION);
267         animator.start();
268         mSwipeAnimator = animator;
269         mTargetedView = targetView;
270     }
271 
272     /**
273      * Phase 2: Move back.
274      */
startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener)275     private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
276         ValueAnimator animator = getAnimatorToRadius(right, 0);
277         animator.addListener(new AnimatorListenerAdapter() {
278             @Override
279             public void onAnimationEnd(Animator animation) {
280                 mSwipeAnimator = null;
281                 mTargetedView = null;
282                 onFinishedListener.run();
283             }
284         });
285         animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
286         animator.setDuration(HINT_PHASE2_DURATION);
287         animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
288         animator.start();
289         mSwipeAnimator = animator;
290     }
291 
getAnimatorToRadius(final boolean right, int radius)292     private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
293         final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
294         ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
295         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
296             @Override
297             public void onAnimationUpdate(ValueAnimator animation) {
298                 float newRadius = (float) animation.getAnimatedValue();
299                 targetView.setCircleRadiusWithoutAnimation(newRadius);
300                 float translation = getTranslationFromRadius(newRadius);
301                 mTranslation = right ? -translation : translation;
302                 updateIconsFromTranslation(targetView);
303             }
304         });
305         return animator;
306     }
307 
cancelAnimation()308     private void cancelAnimation() {
309         if (mSwipeAnimator != null) {
310             mSwipeAnimator.cancel();
311         }
312     }
313 
flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY)314     private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
315         float vel = getCurrentVelocity(lastX, lastY);
316 
317         // We snap back if the current translation is not far enough
318         boolean snapBack = false;
319         if (mCallback.needsAntiFalsing()) {
320             snapBack = snapBack || mFalsingManager.isFalseTouch();
321         }
322         snapBack = snapBack || isBelowFalsingThreshold();
323 
324         // or if the velocity is in the opposite direction.
325         boolean velIsInWrongDirection = vel * mTranslation < 0;
326         snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
327         vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
328         fling(vel, snapBack || forceSnapBack, mTranslation < 0);
329     }
330 
isBelowFalsingThreshold()331     private boolean isBelowFalsingThreshold() {
332         return Math.abs(mTranslation) < Math.abs(mTranslationOnDown) + getMinTranslationAmount();
333     }
334 
getMinTranslationAmount()335     private int getMinTranslationAmount() {
336         float factor = mCallback.getAffordanceFalsingFactor();
337         return (int) (mMinTranslationAmount * factor);
338     }
339 
fling(float vel, final boolean snapBack, boolean right)340     private void fling(float vel, final boolean snapBack, boolean right) {
341         float target = right ? -mCallback.getMaxTranslationDistance()
342                 : mCallback.getMaxTranslationDistance();
343         target = snapBack ? 0 : target;
344 
345         ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
346         mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
347         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
348             @Override
349             public void onAnimationUpdate(ValueAnimator animation) {
350                 mTranslation = (float) animation.getAnimatedValue();
351             }
352         });
353         animator.addListener(mFlingEndListener);
354         if (!snapBack) {
355             startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable, right);
356             mCallback.onAnimationToSideStarted(right, mTranslation, vel);
357         } else {
358             reset(true);
359         }
360         animator.start();
361         mSwipeAnimator = animator;
362         if (snapBack) {
363             mCallback.onSwipingAborted();
364         }
365     }
366 
startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable, boolean right)367     private void startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable,
368             boolean right) {
369         KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
370         targetView.finishAnimation(velocity, animationEndRunnable);
371     }
372 
setTranslation(float translation, boolean isReset, boolean animateReset)373     private void setTranslation(float translation, boolean isReset, boolean animateReset) {
374         translation = rightSwipePossible() ? translation : Math.max(0, translation);
375         translation = leftSwipePossible() ? translation : Math.min(0, translation);
376         float absTranslation = Math.abs(translation);
377         if (translation != mTranslation || isReset) {
378             KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
379             KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
380             float alpha = absTranslation / getMinTranslationAmount();
381 
382             // We interpolate the alpha of the other icons to 0
383             float fadeOutAlpha = 1.0f - alpha;
384             fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
385 
386             boolean animateIcons = isReset && animateReset;
387             boolean forceNoCircleAnimation = isReset && !animateReset;
388             float radius = getRadiusFromTranslation(absTranslation);
389             boolean slowAnimation = isReset && isBelowFalsingThreshold();
390             if (!isReset) {
391                 updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
392                         false, false, false, false);
393             } else {
394                 updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
395                         animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
396             }
397             updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
398                     animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
399 
400             mTranslation = translation;
401         }
402     }
403 
updateIconsFromTranslation(KeyguardAffordanceView targetView)404     private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
405         float absTranslation = Math.abs(mTranslation);
406         float alpha = absTranslation / getMinTranslationAmount();
407 
408         // We interpolate the alpha of the other icons to 0
409         float fadeOutAlpha =  1.0f - alpha;
410         fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
411 
412         // We interpolate the alpha of the targetView to 1
413         KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
414         updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
415         updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
416     }
417 
getTranslationFromRadius(float circleSize)418     private float getTranslationFromRadius(float circleSize) {
419         float translation = (circleSize - mMinBackgroundRadius)
420                 / BACKGROUND_RADIUS_SCALE_FACTOR;
421         return translation > 0.0f ? translation + mTouchSlop : 0.0f;
422     }
423 
getRadiusFromTranslation(float translation)424     private float getRadiusFromTranslation(float translation) {
425         if (translation <= mTouchSlop) {
426             return 0.0f;
427         }
428         return (translation - mTouchSlop)  * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
429     }
430 
animateHideLeftRightIcon()431     public void animateHideLeftRightIcon() {
432         cancelAnimation();
433         updateIcon(mRightIcon, 0f, 0f, true, false, false, false);
434         updateIcon(mLeftIcon, 0f, 0f, true, false, false, false);
435     }
436 
updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha, boolean animate, boolean slowRadiusAnimation, boolean force, boolean forceNoCircleAnimation)437     private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
438                             boolean animate, boolean slowRadiusAnimation, boolean force,
439                             boolean forceNoCircleAnimation) {
440         if (view.getVisibility() != View.VISIBLE && !force) {
441             return;
442         }
443         if (forceNoCircleAnimation) {
444             view.setCircleRadiusWithoutAnimation(circleRadius);
445         } else {
446             view.setCircleRadius(circleRadius, slowRadiusAnimation);
447         }
448         updateIconAlpha(view, alpha, animate);
449     }
450 
updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate)451     private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
452         float scale = getScale(alpha, view);
453         alpha = Math.min(1.0f, alpha);
454         view.setImageAlpha(alpha, animate);
455         view.setImageScale(scale, animate);
456     }
457 
getScale(float alpha, KeyguardAffordanceView icon)458     private float getScale(float alpha, KeyguardAffordanceView icon) {
459         float scale = alpha / icon.getRestingAlpha() * 0.2f +
460                 KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
461         return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
462     }
463 
trackMovement(MotionEvent event)464     private void trackMovement(MotionEvent event) {
465         if (mVelocityTracker != null) {
466             mVelocityTracker.addMovement(event);
467         }
468     }
469 
initVelocityTracker()470     private void initVelocityTracker() {
471         if (mVelocityTracker != null) {
472             mVelocityTracker.recycle();
473         }
474         mVelocityTracker = VelocityTracker.obtain();
475     }
476 
getCurrentVelocity(float lastX, float lastY)477     private float getCurrentVelocity(float lastX, float lastY) {
478         if (mVelocityTracker == null) {
479             return 0;
480         }
481         mVelocityTracker.computeCurrentVelocity(1000);
482         float aX = mVelocityTracker.getXVelocity();
483         float aY = mVelocityTracker.getYVelocity();
484         float bX = lastX - mInitialTouchX;
485         float bY = lastY - mInitialTouchY;
486         float bLen = (float) Math.hypot(bX, bY);
487         // Project the velocity onto the distance vector: a * b / |b|
488         float projectedVelocity = (aX * bX + aY * bY) / bLen;
489         if (mTargetedView == mRightIcon) {
490             projectedVelocity = -projectedVelocity;
491         }
492         return projectedVelocity;
493     }
494 
onConfigurationChanged()495     public void onConfigurationChanged() {
496         initDimens();
497         initIcons();
498     }
499 
onRtlPropertiesChanged()500     public void onRtlPropertiesChanged() {
501         initIcons();
502     }
503 
reset(boolean animate)504     public void reset(boolean animate) {
505         cancelAnimation();
506         setTranslation(0.0f, true /* isReset */, animate);
507         mMotionCancelled = true;
508         if (mSwipingInProgress) {
509             mCallback.onSwipingAborted();
510             mSwipingInProgress = false;
511         }
512     }
513 
isSwipingInProgress()514     public boolean isSwipingInProgress() {
515         return mSwipingInProgress;
516     }
517 
launchAffordance(boolean animate, boolean left)518     public void launchAffordance(boolean animate, boolean left) {
519         if (mSwipingInProgress) {
520             // We don't want to mess with the state if the user is actually swiping already.
521             return;
522         }
523         KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
524         KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
525         startSwiping(targetView);
526 
527         // Do not animate the circle expanding if the affordance isn't visible,
528         // otherwise the circle will be meaningless.
529         if (targetView.getVisibility() != View.VISIBLE) {
530             animate = false;
531         }
532 
533         if (animate) {
534             fling(0, false, !left);
535             updateIcon(otherView, 0.0f, 0, true, false, true, false);
536         } else {
537             mCallback.onAnimationToSideStarted(!left, mTranslation, 0);
538             mTranslation = left ? mCallback.getMaxTranslationDistance()
539                     : mCallback.getMaxTranslationDistance();
540             updateIcon(otherView, 0.0f, 0.0f, false, false, true, false);
541             targetView.instantFinishAnimation();
542             mFlingEndListener.onAnimationEnd(null);
543             mAnimationEndRunnable.run();
544         }
545     }
546 
547     public interface Callback {
548 
549         /**
550          * Notifies the callback when an animation to a side page was started.
551          *
552          * @param rightPage Is the page animated to the right page?
553          */
onAnimationToSideStarted(boolean rightPage, float translation, float vel)554         void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
555 
556         /**
557          * Notifies the callback the animation to a side page has ended.
558          */
onAnimationToSideEnded()559         void onAnimationToSideEnded();
560 
getMaxTranslationDistance()561         float getMaxTranslationDistance();
562 
onSwipingStarted(boolean rightIcon)563         void onSwipingStarted(boolean rightIcon);
564 
onSwipingAborted()565         void onSwipingAborted();
566 
onIconClicked(boolean rightIcon)567         void onIconClicked(boolean rightIcon);
568 
getLeftIcon()569         KeyguardAffordanceView getLeftIcon();
570 
getRightIcon()571         KeyguardAffordanceView getRightIcon();
572 
getLeftPreview()573         View getLeftPreview();
574 
getRightPreview()575         View getRightPreview();
576 
577         /**
578          * @return The factor the minimum swipe amount should be multiplied with.
579          */
getAffordanceFalsingFactor()580         float getAffordanceFalsingFactor();
581 
needsAntiFalsing()582         boolean needsAntiFalsing();
583     }
584 }
585