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