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