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