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