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