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