• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.keyguard;
18 
19 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
20 
21 import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
22 import static com.android.keyguard.LockIconView.ICON_LOCK;
23 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
24 import static com.android.systemui.classifier.Classifier.LOCK_ICON;
25 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
26 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
27 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
28 
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.graphics.Point;
32 import android.graphics.Rect;
33 import android.graphics.drawable.AnimatedStateListDrawable;
34 import android.hardware.biometrics.BiometricSourceType;
35 import android.os.Process;
36 import android.os.VibrationAttributes;
37 import android.util.DisplayMetrics;
38 import android.util.Log;
39 import android.util.MathUtils;
40 import android.view.MotionEvent;
41 import android.view.VelocityTracker;
42 import android.view.View;
43 import android.view.WindowManager;
44 import android.view.accessibility.AccessibilityManager;
45 import android.view.accessibility.AccessibilityNodeInfo;
46 
47 import androidx.annotation.NonNull;
48 import androidx.annotation.Nullable;
49 import androidx.annotation.VisibleForTesting;
50 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
51 
52 import com.android.systemui.Dumpable;
53 import com.android.systemui.R;
54 import com.android.systemui.biometrics.AuthController;
55 import com.android.systemui.biometrics.AuthRippleController;
56 import com.android.systemui.biometrics.UdfpsController;
57 import com.android.systemui.dagger.qualifiers.Main;
58 import com.android.systemui.dump.DumpManager;
59 import com.android.systemui.flags.FeatureFlags;
60 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
61 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
62 import com.android.systemui.keyguard.shared.model.TransitionStep;
63 import com.android.systemui.plugins.FalsingManager;
64 import com.android.systemui.plugins.statusbar.StatusBarStateController;
65 import com.android.systemui.statusbar.StatusBarState;
66 import com.android.systemui.statusbar.VibratorHelper;
67 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
68 import com.android.systemui.statusbar.policy.ConfigurationController;
69 import com.android.systemui.statusbar.policy.KeyguardStateController;
70 import com.android.systemui.util.ViewController;
71 import com.android.systemui.util.concurrency.DelayableExecutor;
72 
73 import java.io.PrintWriter;
74 import java.util.Objects;
75 import java.util.function.Consumer;
76 
77 import javax.inject.Inject;
78 
79 /**
80  * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
81  *
82  * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
83  * icon will show a set distance from the bottom of the device.
84  */
85 @CentralSurfacesComponent.CentralSurfacesScope
86 public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
87     private static final String TAG = "LockIconViewController";
88     private static final float sDefaultDensity =
89             (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
90     private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36);
91     private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
92             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
93 
94     private final long mLongPressTimeout;
95     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
96     @NonNull private final KeyguardViewController mKeyguardViewController;
97     @NonNull private final StatusBarStateController mStatusBarStateController;
98     @NonNull private final KeyguardStateController mKeyguardStateController;
99     @NonNull private final FalsingManager mFalsingManager;
100     @NonNull private final AuthController mAuthController;
101     @NonNull private final AccessibilityManager mAccessibilityManager;
102     @NonNull private final ConfigurationController mConfigurationController;
103     @NonNull private final DelayableExecutor mExecutor;
104     private boolean mUdfpsEnrolled;
105 
106     @NonNull private final AnimatedStateListDrawable mIcon;
107 
108     @NonNull private CharSequence mUnlockedLabel;
109     @NonNull private CharSequence mLockedLabel;
110     @NonNull private final VibratorHelper mVibrator;
111     @Nullable private final AuthRippleController mAuthRippleController;
112     @NonNull private final FeatureFlags mFeatureFlags;
113     @NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
114     @NonNull private final KeyguardInteractor mKeyguardInteractor;
115 
116     // Tracks the velocity of a touch to help filter out the touches that move too fast.
117     private VelocityTracker mVelocityTracker;
118     // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active.
119     private int mActivePointerId = -1;
120 
121     private boolean mIsDozing;
122     private boolean mIsBouncerShowing;
123     private boolean mRunningFPS;
124     private boolean mCanDismissLockScreen;
125     private int mStatusBarState;
126     private boolean mIsKeyguardShowing;
127     private boolean mUserUnlockedWithBiometric;
128     private Runnable mCancelDelayedUpdateVisibilityRunnable;
129     private Runnable mOnGestureDetectedRunnable;
130     private Runnable mLongPressCancelRunnable;
131 
132     private boolean mUdfpsSupported;
133     private float mHeightPixels;
134     private float mWidthPixels;
135     private int mBottomPaddingPx;
136     private int mDefaultPaddingPx;
137 
138     private boolean mShowUnlockIcon;
139     private boolean mShowLockIcon;
140 
141     // for udfps when strong auth is required or unlocked on AOD
142     private boolean mShowAodLockIcon;
143     private boolean mShowAodUnlockedIcon;
144     private final int mMaxBurnInOffsetX;
145     private final int mMaxBurnInOffsetY;
146     private float mInterpolatedDarkAmount;
147 
148     private boolean mDownDetected;
149     private final Rect mSensorTouchLocation = new Rect();
150 
151     @VisibleForTesting
152     final Consumer<TransitionStep> mDozeTransitionCallback = (TransitionStep step) -> {
153         mInterpolatedDarkAmount = step.getValue();
154         mView.setDozeAmount(step.getValue());
155         updateBurnInOffsets();
156     };
157 
158     @VisibleForTesting
159     final Consumer<Boolean> mIsDozingCallback = (Boolean isDozing) -> {
160         mIsDozing = isDozing;
161         updateBurnInOffsets();
162         updateVisibility();
163     };
164 
165     @Inject
LockIconViewController( @ullable LockIconView view, @NonNull StatusBarStateController statusBarStateController, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @NonNull KeyguardViewController keyguardViewController, @NonNull KeyguardStateController keyguardStateController, @NonNull FalsingManager falsingManager, @NonNull AuthController authController, @NonNull DumpManager dumpManager, @NonNull AccessibilityManager accessibilityManager, @NonNull ConfigurationController configurationController, @NonNull @Main DelayableExecutor executor, @NonNull VibratorHelper vibrator, @Nullable AuthRippleController authRippleController, @NonNull @Main Resources resources, @NonNull KeyguardTransitionInteractor transitionInteractor, @NonNull KeyguardInteractor keyguardInteractor, @NonNull FeatureFlags featureFlags )166     public LockIconViewController(
167             @Nullable LockIconView view,
168             @NonNull StatusBarStateController statusBarStateController,
169             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
170             @NonNull KeyguardViewController keyguardViewController,
171             @NonNull KeyguardStateController keyguardStateController,
172             @NonNull FalsingManager falsingManager,
173             @NonNull AuthController authController,
174             @NonNull DumpManager dumpManager,
175             @NonNull AccessibilityManager accessibilityManager,
176             @NonNull ConfigurationController configurationController,
177             @NonNull @Main DelayableExecutor executor,
178             @NonNull VibratorHelper vibrator,
179             @Nullable AuthRippleController authRippleController,
180             @NonNull @Main Resources resources,
181             @NonNull KeyguardTransitionInteractor transitionInteractor,
182             @NonNull KeyguardInteractor keyguardInteractor,
183             @NonNull FeatureFlags featureFlags
184     ) {
185         super(view);
186         mStatusBarStateController = statusBarStateController;
187         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
188         mAuthController = authController;
189         mKeyguardViewController = keyguardViewController;
190         mKeyguardStateController = keyguardStateController;
191         mFalsingManager = falsingManager;
192         mAccessibilityManager = accessibilityManager;
193         mConfigurationController = configurationController;
194         mExecutor = executor;
195         mVibrator = vibrator;
196         mAuthRippleController = authRippleController;
197         mTransitionInteractor = transitionInteractor;
198         mKeyguardInteractor = keyguardInteractor;
199         mFeatureFlags = featureFlags;
200 
201         mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
202         mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
203 
204         mIcon = (AnimatedStateListDrawable)
205                 resources.getDrawable(R.drawable.super_lock_icon, mView.getContext().getTheme());
206         mView.setImageDrawable(mIcon);
207         mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button);
208         mLockedLabel = resources.getString(R.string.accessibility_lock_icon);
209         mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress);
210         dumpManager.registerDumpable(TAG, this);
211     }
212 
213     @Override
onInit()214     protected void onInit() {
215         mView.setAccessibilityDelegate(mAccessibilityDelegate);
216 
217         if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
218             collectFlow(mView, mTransitionInteractor.getDozeAmountTransition(),
219                     mDozeTransitionCallback);
220             collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback);
221         }
222     }
223 
224     @Override
onViewAttached()225     protected void onViewAttached() {
226         updateIsUdfpsEnrolled();
227         updateConfiguration();
228         updateKeyguardShowing();
229         mUserUnlockedWithBiometric = false;
230 
231         mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
232         mIsDozing = mStatusBarStateController.isDozing();
233         mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount();
234         mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
235         mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
236         mStatusBarState = mStatusBarStateController.getState();
237 
238         updateColors();
239         mConfigurationController.addCallback(mConfigurationListener);
240 
241         mAuthController.addCallback(mAuthControllerCallback);
242         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
243         mStatusBarStateController.addCallback(mStatusBarStateListener);
244         mKeyguardStateController.addCallback(mKeyguardStateCallback);
245         mDownDetected = false;
246         updateBurnInOffsets();
247         updateVisibility();
248 
249         mAccessibilityManager.addAccessibilityStateChangeListener(
250                 mAccessibilityStateChangeListener);
251         updateAccessibility();
252     }
253 
updateAccessibility()254     private void updateAccessibility() {
255         if (mAccessibilityManager.isEnabled()) {
256             mView.setOnClickListener(mA11yClickListener);
257         } else {
258             mView.setOnClickListener(null);
259         }
260     }
261 
262     @Override
onViewDetached()263     protected void onViewDetached() {
264         mAuthController.removeCallback(mAuthControllerCallback);
265         mConfigurationController.removeCallback(mConfigurationListener);
266         mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
267         mStatusBarStateController.removeCallback(mStatusBarStateListener);
268         mKeyguardStateController.removeCallback(mKeyguardStateCallback);
269 
270         if (mCancelDelayedUpdateVisibilityRunnable != null) {
271             mCancelDelayedUpdateVisibilityRunnable.run();
272             mCancelDelayedUpdateVisibilityRunnable = null;
273         }
274 
275         mAccessibilityManager.removeAccessibilityStateChangeListener(
276                 mAccessibilityStateChangeListener);
277     }
278 
getTop()279     public float getTop() {
280         return mView.getLocationTop();
281     }
282 
getBottom()283     public float getBottom() {
284         return mView.getLocationBottom();
285     }
286 
updateVisibility()287     private void updateVisibility() {
288         if (mCancelDelayedUpdateVisibilityRunnable != null) {
289             mCancelDelayedUpdateVisibilityRunnable.run();
290             mCancelDelayedUpdateVisibilityRunnable = null;
291         }
292 
293         if (!mIsKeyguardShowing && !mIsDozing) {
294             mView.setVisibility(View.INVISIBLE);
295             return;
296         }
297 
298         boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon
299                 && !mShowAodUnlockedIcon && !mShowAodLockIcon;
300         mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
301                 && (!mUdfpsEnrolled || !mRunningFPS);
302         mShowUnlockIcon = (mCanDismissLockScreen || mUserUnlockedWithBiometric) && isLockScreen();
303         mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen;
304         mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen;
305 
306         final CharSequence prevContentDescription = mView.getContentDescription();
307         if (mShowLockIcon) {
308             mView.updateIcon(ICON_LOCK, false);
309             mView.setContentDescription(mLockedLabel);
310             mView.setVisibility(View.VISIBLE);
311         } else if (mShowUnlockIcon) {
312             if (wasShowingFpIcon) {
313                 // fp icon was shown by UdfpsView, and now we still want to animate the transition
314                 // in this drawable
315                 mView.updateIcon(ICON_FINGERPRINT, false);
316             }
317             mView.updateIcon(ICON_UNLOCK, false);
318             mView.setContentDescription(mUnlockedLabel);
319             mView.setVisibility(View.VISIBLE);
320         } else if (mShowAodUnlockedIcon) {
321             mView.updateIcon(ICON_UNLOCK, true);
322             mView.setContentDescription(mUnlockedLabel);
323             mView.setVisibility(View.VISIBLE);
324         } else if (mShowAodLockIcon) {
325             mView.updateIcon(ICON_LOCK, true);
326             mView.setContentDescription(mLockedLabel);
327             mView.setVisibility(View.VISIBLE);
328         } else {
329             mView.clearIcon();
330             mView.setVisibility(View.INVISIBLE);
331             mView.setContentDescription(null);
332         }
333 
334         if (!Objects.equals(prevContentDescription, mView.getContentDescription())
335                 && mView.getContentDescription() != null) {
336             mView.announceForAccessibility(mView.getContentDescription());
337         }
338     }
339 
340     private final View.AccessibilityDelegate mAccessibilityDelegate =
341             new View.AccessibilityDelegate() {
342         private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
343                 new AccessibilityNodeInfo.AccessibilityAction(
344                         AccessibilityNodeInfoCompat.ACTION_CLICK,
345                         getResources().getString(R.string.accessibility_authenticate_hint));
346         private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint =
347                 new AccessibilityNodeInfo.AccessibilityAction(
348                         AccessibilityNodeInfoCompat.ACTION_CLICK,
349                         getResources().getString(R.string.accessibility_enter_hint));
350         public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
351             super.onInitializeAccessibilityNodeInfo(v, info);
352             if (isActionable()) {
353                 if (mShowLockIcon) {
354                     info.addAction(mAccessibilityAuthenticateHint);
355                 } else if (mShowUnlockIcon) {
356                     info.addAction(mAccessibilityEnterHint);
357                 }
358             }
359         }
360     };
361 
isLockScreen()362     private boolean isLockScreen() {
363         return !mIsDozing
364                 && !mIsBouncerShowing
365                 && mStatusBarState == StatusBarState.KEYGUARD;
366     }
367 
updateKeyguardShowing()368     private void updateKeyguardShowing() {
369         mIsKeyguardShowing = mKeyguardStateController.isShowing()
370                 && !mKeyguardStateController.isKeyguardGoingAway();
371     }
372 
updateColors()373     private void updateColors() {
374         mView.updateColorAndBackgroundVisibility();
375     }
376 
updateConfiguration()377     private void updateConfiguration() {
378         WindowManager windowManager = getContext().getSystemService(WindowManager.class);
379         Rect bounds = windowManager.getCurrentWindowMetrics().getBounds();
380         mWidthPixels = bounds.right;
381         mHeightPixels = bounds.bottom;
382         mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom);
383         mDefaultPaddingPx =
384                 getResources().getDimensionPixelSize(R.dimen.lock_icon_padding);
385 
386         mUnlockedLabel = mView.getContext().getResources().getString(
387                 R.string.accessibility_unlock_button);
388         mLockedLabel = mView.getContext()
389                 .getResources().getString(R.string.accessibility_lock_icon);
390         updateLockIconLocation();
391     }
392 
updateLockIconLocation()393     private void updateLockIconLocation() {
394         final float scaleFactor = mAuthController.getScaleFactor();
395         final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
396         if (mUdfpsSupported) {
397             mView.setCenterLocation(mAuthController.getUdfpsLocation(),
398                     mAuthController.getUdfpsRadius(), scaledPadding);
399         } else {
400             mView.setCenterLocation(
401                     new Point((int) mWidthPixels / 2,
402                             (int) (mHeightPixels
403                                     - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
404                         sLockIconRadiusPx * scaleFactor, scaledPadding);
405         }
406     }
407 
408     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)409     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
410         pw.println("mUdfpsSupported: " + mUdfpsSupported);
411         pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
412         pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
413         pw.println(" mIcon: ");
414         for (int state : mIcon.getState()) {
415             pw.print(" " + state);
416         }
417         pw.println();
418         pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
419         pw.println(" mShowLockIcon: " + mShowLockIcon);
420         pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon);
421         pw.println();
422         pw.println(" mIsDozing: " + mIsDozing);
423         pw.println(" isFlagEnabled(DOZING_MIGRATION_1): "
424                 + mFeatureFlags.isEnabled(DOZING_MIGRATION_1));
425         pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
426         pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
427         pw.println(" mRunningFPS: " + mRunningFPS);
428         pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
429         pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
430         pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
431         pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
432         pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx);
433 
434         if (mView != null) {
435             mView.dump(pw, args);
436         }
437     }
438 
439     /** Every minute, update the aod icon's burn in offset */
dozeTimeTick()440     public void dozeTimeTick() {
441         updateBurnInOffsets();
442     }
443 
updateBurnInOffsets()444     private void updateBurnInOffsets() {
445         float offsetX = MathUtils.lerp(0f,
446                 getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
447                         - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
448         float offsetY = MathUtils.lerp(0f,
449                 getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
450                         - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
451 
452         mView.setTranslationX(offsetX);
453         mView.setTranslationY(offsetY);
454     }
455 
updateIsUdfpsEnrolled()456     private void updateIsUdfpsEnrolled() {
457         boolean wasUdfpsSupported = mUdfpsSupported;
458         boolean wasUdfpsEnrolled = mUdfpsEnrolled;
459 
460         mUdfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported();
461         mView.setUseBackground(mUdfpsSupported);
462 
463         mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
464         if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
465             updateVisibility();
466         }
467     }
468 
469     /**
470      * @return whether the userUnlockedWithBiometric state changed
471      */
updateUserUnlockedWithBiometric()472     private boolean updateUserUnlockedWithBiometric() {
473         final boolean wasUserUnlockedWithBiometric = mUserUnlockedWithBiometric;
474         mUserUnlockedWithBiometric =
475                 mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
476                         KeyguardUpdateMonitor.getCurrentUser());
477         return wasUserUnlockedWithBiometric != mUserUnlockedWithBiometric;
478     }
479 
480     private StatusBarStateController.StateListener mStatusBarStateListener =
481             new StatusBarStateController.StateListener() {
482                 @Override
483                 public void onDozeAmountChanged(float linear, float eased) {
484                     if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
485                         mInterpolatedDarkAmount = eased;
486                         mView.setDozeAmount(eased);
487                         updateBurnInOffsets();
488                     }
489                 }
490 
491                 @Override
492                 public void onDozingChanged(boolean isDozing) {
493                     if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
494                         mIsDozing = isDozing;
495                         updateBurnInOffsets();
496                         updateVisibility();
497                     }
498                 }
499 
500                 @Override
501                 public void onStateChanged(int statusBarState) {
502                     mStatusBarState = statusBarState;
503                     updateVisibility();
504                 }
505             };
506 
507     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
508             new KeyguardUpdateMonitorCallback() {
509                 @Override
510                 public void onKeyguardBouncerStateChanged(boolean bouncer) {
511                     mIsBouncerShowing = bouncer;
512                     updateVisibility();
513                 }
514 
515                 @Override
516                 public void onBiometricsCleared() {
517                     if (updateUserUnlockedWithBiometric()) {
518                         updateVisibility();
519                     }
520                 }
521 
522                 @Override
523                 public void onBiometricRunningStateChanged(boolean running,
524                         BiometricSourceType biometricSourceType) {
525                     final boolean wasRunningFps = mRunningFPS;
526                     final boolean userUnlockedWithBiometricChanged =
527                             updateUserUnlockedWithBiometric();
528 
529                     if (biometricSourceType == FINGERPRINT) {
530                         mRunningFPS = running;
531                         if (wasRunningFps && !mRunningFPS) {
532                             if (mCancelDelayedUpdateVisibilityRunnable != null) {
533                                 mCancelDelayedUpdateVisibilityRunnable.run();
534                             }
535 
536                             // For some devices, auth is cancelled immediately on screen off but
537                             // before dozing state is set. We want to avoid briefly showing the
538                             // button in this case, so we delay updating the visibility by 50ms.
539                             mCancelDelayedUpdateVisibilityRunnable =
540                                     mExecutor.executeDelayed(() -> updateVisibility(), 50);
541                             return;
542                         }
543                     }
544 
545                     if (userUnlockedWithBiometricChanged || wasRunningFps != mRunningFPS) {
546                         updateVisibility();
547                     }
548                 }
549             };
550 
551     private final KeyguardStateController.Callback mKeyguardStateCallback =
552             new KeyguardStateController.Callback() {
553         @Override
554         public void onUnlockedChanged() {
555             mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
556             updateUserUnlockedWithBiometric();
557             updateKeyguardShowing();
558             updateVisibility();
559         }
560 
561         @Override
562         public void onKeyguardShowingChanged() {
563             // Reset values in case biometrics were removed (ie: pin/pattern/password => swipe).
564             // If biometrics were removed, local vars mCanDismissLockScreen and
565             // mUserUnlockedWithBiometric may not be updated.
566             mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
567 
568             // reset mIsBouncerShowing state in case it was preemptively set
569             // onLongPress
570             mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
571 
572             updateKeyguardShowing();
573             if (mIsKeyguardShowing) {
574                 updateUserUnlockedWithBiometric();
575             }
576             updateVisibility();
577         }
578 
579         @Override
580         public void onKeyguardFadingAwayChanged() {
581             updateKeyguardShowing();
582             updateVisibility();
583         }
584     };
585 
586     private final ConfigurationController.ConfigurationListener mConfigurationListener =
587             new ConfigurationController.ConfigurationListener() {
588         @Override
589         public void onUiModeChanged() {
590             updateColors();
591         }
592 
593         @Override
594         public void onThemeChanged() {
595             updateColors();
596         }
597 
598         @Override
599         public void onConfigChanged(Configuration newConfig) {
600             updateConfiguration();
601             updateColors();
602         }
603     };
604 
605     /**
606      * Handles the touch if it is within the lock icon view and {@link #isActionable()} is true.
607      * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon
608      * area for {@link #mLongPressTimeout} ms.
609      *
610      * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}.
611      */
onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable)612     public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
613         if (!onInterceptTouchEvent(event)) {
614             cancelTouches();
615             return false;
616         }
617 
618         mOnGestureDetectedRunnable = onGestureDetectedRunnable;
619         switch(event.getActionMasked()) {
620             case MotionEvent.ACTION_DOWN:
621             case MotionEvent.ACTION_HOVER_ENTER:
622                 if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
623                     mVibrator.vibrate(
624                             Process.myUid(),
625                             getContext().getOpPackageName(),
626                             UdfpsController.EFFECT_CLICK,
627                             "lock-icon-down",
628                             TOUCH_VIBRATION_ATTRIBUTES);
629                 }
630 
631                 // The pointer that causes ACTION_DOWN is always at index 0.
632                 // We need to persist its ID to track it during ACTION_MOVE that could include
633                 // data for many other pointers because of multi-touch support.
634                 mActivePointerId = event.getPointerId(0);
635                 if (mVelocityTracker == null) {
636                     // To simplify the lifecycle of the velocity tracker, make sure it's never null
637                     // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
638                     mVelocityTracker = VelocityTracker.obtain();
639                 } else {
640                     // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
641                     // ACTION_DOWN, in that case we should just reuse the old instance.
642                     mVelocityTracker.clear();
643                 }
644                 mVelocityTracker.addMovement(event);
645 
646                 mDownDetected = true;
647                 mLongPressCancelRunnable = mExecutor.executeDelayed(
648                         this::onLongPress, mLongPressTimeout);
649                 break;
650             case MotionEvent.ACTION_MOVE:
651             case MotionEvent.ACTION_HOVER_MOVE:
652                 mVelocityTracker.addMovement(event);
653                 // Compute pointer velocity in pixels per second.
654                 mVelocityTracker.computeCurrentVelocity(1000);
655                 float velocity = UdfpsController.computePointerSpeed(mVelocityTracker,
656                         mActivePointerId);
657                 if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS
658                         && UdfpsController.exceedsVelocityThreshold(velocity)) {
659                     Log.v(TAG, "lock icon long-press rescheduled due to "
660                             + "high pointer velocity=" + velocity);
661                     mLongPressCancelRunnable.run();
662                     mLongPressCancelRunnable = mExecutor.executeDelayed(
663                             this::onLongPress, mLongPressTimeout);
664                 }
665                 break;
666             case MotionEvent.ACTION_UP:
667             case MotionEvent.ACTION_CANCEL:
668             case MotionEvent.ACTION_HOVER_EXIT:
669                 cancelTouches();
670                 break;
671         }
672 
673         return true;
674     }
675 
676     /**
677      * Intercepts the touch if the onDown event and current event are within this lock icon view's
678      * bounds.
679      */
onInterceptTouchEvent(MotionEvent event)680     public boolean onInterceptTouchEvent(MotionEvent event) {
681         if (!inLockIconArea(event) || !isActionable()) {
682             return false;
683         }
684 
685         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
686             return true;
687         }
688 
689         return mDownDetected;
690     }
691 
onLongPress()692     private void onLongPress() {
693         cancelTouches();
694         if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
695             Log.v(TAG, "lock icon long-press rejected by the falsing manager.");
696             return;
697         }
698 
699         // pre-emptively set to true to hide view
700         mIsBouncerShowing = true;
701         if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
702             mAuthRippleController.showUnlockRipple(FINGERPRINT);
703         }
704         updateVisibility();
705         if (mOnGestureDetectedRunnable != null) {
706             mOnGestureDetectedRunnable.run();
707         }
708 
709         // play device entry haptic (same as biometric success haptic)
710         mVibrator.vibrate(
711                 Process.myUid(),
712                 getContext().getOpPackageName(),
713                 UdfpsController.EFFECT_CLICK,
714                 "lock-screen-lock-icon-longpress",
715                 TOUCH_VIBRATION_ATTRIBUTES);
716 
717         mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
718     }
719 
720 
cancelTouches()721     private void cancelTouches() {
722         mDownDetected = false;
723         if (mLongPressCancelRunnable != null) {
724             mLongPressCancelRunnable.run();
725         }
726         if (mVelocityTracker != null) {
727             mVelocityTracker.recycle();
728             mVelocityTracker = null;
729         }
730     }
731 
inLockIconArea(MotionEvent event)732     private boolean inLockIconArea(MotionEvent event) {
733         mView.getHitRect(mSensorTouchLocation);
734         return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
735                 && mView.getVisibility() == View.VISIBLE;
736     }
737 
isActionable()738     private boolean isActionable() {
739         if (mIsBouncerShowing) {
740             Log.v(TAG, "lock icon long-press ignored, bouncer already showing.");
741             // a long press gestures from AOD may have already triggered the bouncer to show,
742             // so this touch is no longer actionable
743             return false;
744         }
745         return mUdfpsSupported || mShowUnlockIcon;
746     }
747 
748     /**
749      * Set the alpha of this view.
750      */
setAlpha(float alpha)751     public void setAlpha(float alpha) {
752         mView.setAlpha(alpha);
753     }
754 
updateUdfpsConfig()755     private void updateUdfpsConfig() {
756         // must be called from the main thread since it may update the views
757         mExecutor.execute(() -> {
758             updateIsUdfpsEnrolled();
759             updateConfiguration();
760         });
761     }
762 
763     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
764         @Override
765         public void onAllAuthenticatorsRegistered() {
766             updateUdfpsConfig();
767         }
768 
769         @Override
770         public void onEnrollmentsChanged() {
771             updateUdfpsConfig();
772         }
773 
774         @Override
775         public void onUdfpsLocationChanged() {
776             updateUdfpsConfig();
777         }
778     };
779 
780     private final View.OnClickListener mA11yClickListener = v -> onLongPress();
781 
782     private final AccessibilityManager.AccessibilityStateChangeListener
783             mAccessibilityStateChangeListener = enabled -> updateAccessibility();
784 }
785