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.systemui.biometrics; 18 19 import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD; 20 21 import static com.android.internal.util.Preconditions.checkArgument; 22 import static com.android.internal.util.Preconditions.checkNotNull; 23 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; 24 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.SuppressLint; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.graphics.PixelFormat; 33 import android.graphics.Point; 34 import android.graphics.RectF; 35 import android.hardware.biometrics.BiometricOverlayConstants; 36 import android.hardware.biometrics.SensorLocationInternal; 37 import android.hardware.display.DisplayManager; 38 import android.hardware.fingerprint.FingerprintManager; 39 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; 40 import android.hardware.fingerprint.IUdfpsOverlayController; 41 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; 42 import android.media.AudioAttributes; 43 import android.os.Handler; 44 import android.os.PowerManager; 45 import android.os.Process; 46 import android.os.RemoteException; 47 import android.os.Trace; 48 import android.os.VibrationEffect; 49 import android.os.Vibrator; 50 import android.util.Log; 51 import android.view.Gravity; 52 import android.view.LayoutInflater; 53 import android.view.MotionEvent; 54 import android.view.Surface; 55 import android.view.VelocityTracker; 56 import android.view.View; 57 import android.view.WindowManager; 58 import android.view.accessibility.AccessibilityManager; 59 60 import com.android.internal.annotations.VisibleForTesting; 61 import com.android.keyguard.KeyguardUpdateMonitor; 62 import com.android.systemui.R; 63 import com.android.systemui.dagger.SysUISingleton; 64 import com.android.systemui.dagger.qualifiers.Main; 65 import com.android.systemui.doze.DozeReceiver; 66 import com.android.systemui.dump.DumpManager; 67 import com.android.systemui.keyguard.ScreenLifecycle; 68 import com.android.systemui.plugins.FalsingManager; 69 import com.android.systemui.plugins.statusbar.StatusBarStateController; 70 import com.android.systemui.statusbar.LockscreenShadeTransitionController; 71 import com.android.systemui.statusbar.phone.KeyguardBypassController; 72 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 73 import com.android.systemui.statusbar.phone.SystemUIDialogManager; 74 import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; 75 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; 76 import com.android.systemui.statusbar.policy.ConfigurationController; 77 import com.android.systemui.statusbar.policy.KeyguardStateController; 78 import com.android.systemui.util.concurrency.DelayableExecutor; 79 import com.android.systemui.util.concurrency.Execution; 80 import com.android.systemui.util.time.SystemClock; 81 82 import java.util.HashSet; 83 import java.util.Optional; 84 import java.util.Set; 85 86 import javax.inject.Inject; 87 88 import kotlin.Unit; 89 90 /** 91 * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events, 92 * and coordinates triggering of the high-brightness mode (HBM). 93 * 94 * Note that the current architecture is designed so that a single {@link UdfpsController} 95 * controls/manages all UDFPS sensors. In other words, a single controller is registered with 96 * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such 97 * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or 98 * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have 99 * {@code sensorId} parameters. 100 */ 101 @SuppressWarnings("deprecation") 102 @SysUISingleton 103 public class UdfpsController implements DozeReceiver { 104 private static final String TAG = "UdfpsController"; 105 private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000; 106 private static final long DEFAULT_VIBRATION_DURATION = 1000; // milliseconds 107 108 // Minimum required delay between consecutive touch logs in milliseconds. 109 private static final long MIN_TOUCH_LOG_INTERVAL = 50; 110 111 private final Context mContext; 112 private final Execution mExecution; 113 private final FingerprintManager mFingerprintManager; 114 @NonNull private final LayoutInflater mInflater; 115 private final WindowManager mWindowManager; 116 private final DelayableExecutor mFgExecutor; 117 @NonNull private final PanelExpansionStateManager mPanelExpansionStateManager; 118 @NonNull private final StatusBarStateController mStatusBarStateController; 119 @NonNull private final KeyguardStateController mKeyguardStateController; 120 @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; 121 @NonNull private final DumpManager mDumpManager; 122 @NonNull private final SystemUIDialogManager mDialogManager; 123 @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 124 @Nullable private final Vibrator mVibrator; 125 @NonNull private final FalsingManager mFalsingManager; 126 @NonNull private final PowerManager mPowerManager; 127 @NonNull private final AccessibilityManager mAccessibilityManager; 128 @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; 129 @Nullable private final UdfpsHbmProvider mHbmProvider; 130 @NonNull private final KeyguardBypassController mKeyguardBypassController; 131 @NonNull private final ConfigurationController mConfigurationController; 132 @NonNull private final SystemClock mSystemClock; 133 @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; 134 @NonNull private final UnlockedScreenOffAnimationController 135 mUnlockedScreenOffAnimationController; 136 // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple 137 // sensors, this, in addition to a lot of the code here, will be updated. 138 @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; 139 private final WindowManager.LayoutParams mCoreLayoutParams; 140 141 // Tracks the velocity of a touch to help filter out the touches that move too fast. 142 @Nullable private VelocityTracker mVelocityTracker; 143 // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. 144 private int mActivePointerId = -1; 145 // The timestamp of the most recent touch log. 146 private long mTouchLogTime; 147 // Sensor has a good capture for this touch. Do not need to illuminate for this particular 148 // touch event anymore. In other words, do not illuminate until user lifts and touches the 149 // sensor area again. 150 // TODO: We should probably try to make touch/illumination things more of a FSM 151 private boolean mGoodCaptureReceived; 152 153 @Nullable private UdfpsView mView; 154 // The current request from FingerprintService. Null if no current request. 155 @Nullable ServerRequest mServerRequest; 156 157 // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when 158 // to turn off high brightness mode. To get around this limitation, the state of the AOD 159 // interrupt is being tracked and a timeout is used as a last resort to turn off high brightness 160 // mode. 161 private boolean mIsAodInterruptActive; 162 @Nullable private Runnable mCancelAodTimeoutAction; 163 private boolean mScreenOn; 164 private Runnable mAodInterruptRunnable; 165 private boolean mOnFingerDown; 166 private boolean mAttemptedToDismissKeyguard; 167 private Set<Callback> mCallbacks = new HashSet<>(); 168 169 @VisibleForTesting 170 public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = 171 new AudioAttributes.Builder() 172 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 173 // vibration will bypass battery saver mode: 174 .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY) 175 .build(); 176 177 // haptic to use for successful device entry 178 public static final VibrationEffect EFFECT_CLICK = 179 VibrationEffect.get(VibrationEffect.EFFECT_CLICK); 180 181 private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { 182 @Override 183 public void onScreenTurnedOn() { 184 mScreenOn = true; 185 if (mAodInterruptRunnable != null) { 186 mAodInterruptRunnable.run(); 187 mAodInterruptRunnable = null; 188 } 189 } 190 191 @Override 192 public void onScreenTurnedOff() { 193 mScreenOn = false; 194 } 195 }; 196 197 /** 198 * Keeps track of state within a single FingerprintService request. Note that this state 199 * persists across configuration changes, etc, since it is considered a single request. 200 * 201 * TODO: Perhaps we can move more global variables into here 202 */ 203 private static class ServerRequest { 204 // Reason the overlay has been requested. See IUdfpsOverlayController for definitions. 205 final int mRequestReason; 206 @NonNull final IUdfpsOverlayControllerCallback mCallback; 207 @Nullable final UdfpsEnrollHelper mEnrollHelper; 208 ServerRequest(int requestReason, @NonNull IUdfpsOverlayControllerCallback callback, @Nullable UdfpsEnrollHelper enrollHelper)209 ServerRequest(int requestReason, @NonNull IUdfpsOverlayControllerCallback callback, 210 @Nullable UdfpsEnrollHelper enrollHelper) { 211 mRequestReason = requestReason; 212 mCallback = callback; 213 mEnrollHelper = enrollHelper; 214 } 215 onEnrollmentProgress(int remaining)216 void onEnrollmentProgress(int remaining) { 217 if (mEnrollHelper != null) { 218 mEnrollHelper.onEnrollmentProgress(remaining); 219 } 220 } 221 onAcquiredGood()222 void onAcquiredGood() { 223 if (mEnrollHelper != null) { 224 mEnrollHelper.animateIfLastStep(); 225 } 226 } 227 onEnrollmentHelp()228 void onEnrollmentHelp() { 229 if (mEnrollHelper != null) { 230 mEnrollHelper.onEnrollmentHelp(); 231 } 232 } 233 onUserCanceled()234 void onUserCanceled() { 235 try { 236 mCallback.onUserCanceled(); 237 } catch (RemoteException e) { 238 Log.e(TAG, "Remote exception", e); 239 } 240 } 241 } 242 243 public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { 244 @Override showUdfpsOverlay(int sensorId, int reason, @NonNull IUdfpsOverlayControllerCallback callback)245 public void showUdfpsOverlay(int sensorId, int reason, 246 @NonNull IUdfpsOverlayControllerCallback callback) { 247 mFgExecutor.execute(() -> { 248 final UdfpsEnrollHelper enrollHelper; 249 if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR 250 || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) { 251 enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason); 252 } else { 253 enrollHelper = null; 254 } 255 mServerRequest = new ServerRequest(reason, callback, enrollHelper); 256 updateOverlay(); 257 }); 258 } 259 260 @Override hideUdfpsOverlay(int sensorId)261 public void hideUdfpsOverlay(int sensorId) { 262 mFgExecutor.execute(() -> { 263 if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { 264 // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState 265 // to be updated shortly afterwards 266 Log.d(TAG, "hiding udfps overlay when " 267 + "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true"); 268 } 269 270 mServerRequest = null; 271 updateOverlay(); 272 }); 273 } 274 275 @Override onAcquiredGood(int sensorId)276 public void onAcquiredGood(int sensorId) { 277 mFgExecutor.execute(() -> { 278 if (mView == null) { 279 Log.e(TAG, "Null view when onAcquiredGood for sensorId: " + sensorId); 280 return; 281 } 282 mGoodCaptureReceived = true; 283 mView.stopIllumination(); 284 if (mServerRequest != null) { 285 mServerRequest.onAcquiredGood(); 286 } else { 287 Log.e(TAG, "Null serverRequest when onAcquiredGood"); 288 } 289 }); 290 } 291 292 @Override onEnrollmentProgress(int sensorId, int remaining)293 public void onEnrollmentProgress(int sensorId, int remaining) { 294 mFgExecutor.execute(() -> { 295 if (mServerRequest == null) { 296 Log.e(TAG, "onEnrollProgress received but serverRequest is null"); 297 return; 298 } 299 mServerRequest.onEnrollmentProgress(remaining); 300 }); 301 } 302 303 @Override onEnrollmentHelp(int sensorId)304 public void onEnrollmentHelp(int sensorId) { 305 mFgExecutor.execute(() -> { 306 if (mServerRequest == null) { 307 Log.e(TAG, "onEnrollmentHelp received but serverRequest is null"); 308 return; 309 } 310 mServerRequest.onEnrollmentHelp(); 311 }); 312 } 313 314 @Override setDebugMessage(int sensorId, String message)315 public void setDebugMessage(int sensorId, String message) { 316 mFgExecutor.execute(() -> { 317 if (mView == null) { 318 return; 319 } 320 mView.setDebugMessage(message); 321 }); 322 } 323 } 324 325 /** 326 * Calculate the pointer speed given a velocity tracker and the pointer id. 327 * This assumes that the velocity tracker has already been passed all relevant motion events. 328 */ computePointerSpeed(@onNull VelocityTracker tracker, int pointerId)329 public static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { 330 final float vx = tracker.getXVelocity(pointerId); 331 final float vy = tracker.getYVelocity(pointerId); 332 return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); 333 } 334 335 /** 336 * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. 337 */ exceedsVelocityThreshold(float velocity)338 public static boolean exceedsVelocityThreshold(float velocity) { 339 return velocity > 750f; 340 } 341 342 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 343 @Override 344 public void onReceive(Context context, Intent intent) { 345 if (mServerRequest != null 346 && mServerRequest.mRequestReason != REASON_AUTH_KEYGUARD 347 && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { 348 Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: " 349 + mServerRequest.mRequestReason); 350 mServerRequest.onUserCanceled(); 351 mServerRequest = null; 352 updateOverlay(); 353 } 354 } 355 }; 356 357 /** 358 * Forwards touches to the udfps controller / view 359 */ onTouch(MotionEvent event)360 public boolean onTouch(MotionEvent event) { 361 if (mView == null) { 362 return false; 363 } 364 return onTouch(mView, event, false); 365 } 366 367 @SuppressLint("ClickableViewAccessibility") 368 private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> 369 onTouch(view, event, true); 370 371 @SuppressLint("ClickableViewAccessibility") 372 private final UdfpsView.OnHoverListener mOnHoverListener = (view, event) -> 373 onTouch(view, event, true); 374 375 private final AccessibilityManager.TouchExplorationStateChangeListener 376 mTouchExplorationStateChangeListener = enabled -> updateTouchListener(); 377 378 /** 379 * @param x coordinate 380 * @param y coordinate 381 * @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else, 382 * calculate from the display dimensions in portrait orientation 383 */ isWithinSensorArea(UdfpsView udfpsView, float x, float y, boolean relativeToUdfpsView)384 private boolean isWithinSensorArea(UdfpsView udfpsView, float x, float y, 385 boolean relativeToUdfpsView) { 386 if (relativeToUdfpsView) { 387 // TODO: move isWithinSensorArea to UdfpsController. 388 return udfpsView.isWithinSensorArea(x, y); 389 } 390 391 if (mView == null || mView.getAnimationViewController() == null) { 392 return false; 393 } 394 395 return !mView.getAnimationViewController().shouldPauseAuth() 396 && getSensorLocation().contains(x, y); 397 } 398 onTouch(View view, MotionEvent event, boolean fromUdfpsView)399 private boolean onTouch(View view, MotionEvent event, boolean fromUdfpsView) { 400 UdfpsView udfpsView = (UdfpsView) view; 401 final boolean isIlluminationRequested = udfpsView.isIlluminationRequested(); 402 boolean handled = false; 403 switch (event.getActionMasked()) { 404 case MotionEvent.ACTION_OUTSIDE: 405 udfpsView.onTouchOutsideView(); 406 return true; 407 case MotionEvent.ACTION_DOWN: 408 case MotionEvent.ACTION_HOVER_ENTER: 409 Trace.beginSection("UdfpsController.onTouch.ACTION_DOWN"); 410 // To simplify the lifecycle of the velocity tracker, make sure it's never null 411 // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. 412 if (mVelocityTracker == null) { 413 mVelocityTracker = VelocityTracker.obtain(); 414 } else { 415 // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new 416 // ACTION_DOWN, in that case we should just reuse the old instance. 417 mVelocityTracker.clear(); 418 } 419 420 boolean withinSensorArea = 421 isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView); 422 if (withinSensorArea) { 423 Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0); 424 Log.v(TAG, "onTouch | action down"); 425 // The pointer that causes ACTION_DOWN is always at index 0. 426 // We need to persist its ID to track it during ACTION_MOVE that could include 427 // data for many other pointers because of multi-touch support. 428 mActivePointerId = event.getPointerId(0); 429 mVelocityTracker.addMovement(event); 430 handled = true; 431 } 432 if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) { 433 Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN"); 434 if (!mOnFingerDown) { 435 playStartHaptic(); 436 } 437 mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */); 438 mAttemptedToDismissKeyguard = true; 439 } 440 Trace.endSection(); 441 break; 442 443 case MotionEvent.ACTION_MOVE: 444 case MotionEvent.ACTION_HOVER_MOVE: 445 Trace.beginSection("UdfpsController.onTouch.ACTION_MOVE"); 446 final int idx = mActivePointerId == -1 447 ? event.getPointerId(0) 448 : event.findPointerIndex(mActivePointerId); 449 if (idx == event.getActionIndex()) { 450 boolean actionMoveWithinSensorArea = 451 isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx), 452 fromUdfpsView); 453 if ((fromUdfpsView || actionMoveWithinSensorArea) 454 && shouldTryToDismissKeyguard()) { 455 Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE"); 456 if (!mOnFingerDown) { 457 playStartHaptic(); 458 } 459 mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */); 460 mAttemptedToDismissKeyguard = true; 461 break; 462 } 463 if (actionMoveWithinSensorArea) { 464 if (mVelocityTracker == null) { 465 // touches could be injected, so the velocity tracker may not have 466 // been initialized (via ACTION_DOWN). 467 mVelocityTracker = VelocityTracker.obtain(); 468 } 469 mVelocityTracker.addMovement(event); 470 // Compute pointer velocity in pixels per second. 471 mVelocityTracker.computeCurrentVelocity(1000); 472 // Compute pointer speed from X and Y velocities. 473 final float v = computePointerSpeed(mVelocityTracker, mActivePointerId); 474 final float minor = event.getTouchMinor(idx); 475 final float major = event.getTouchMajor(idx); 476 final boolean exceedsVelocityThreshold = exceedsVelocityThreshold(v); 477 final String touchInfo = String.format( 478 "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b", 479 minor, major, v, exceedsVelocityThreshold); 480 final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; 481 if (!isIlluminationRequested && !mGoodCaptureReceived && 482 !exceedsVelocityThreshold) { 483 final int rawX = (int) event.getRawX(); 484 final int rawY = (int) event.getRawY(); 485 // Default coordinates assume portrait mode. 486 int x = rawX; 487 int y = rawY; 488 489 // Gets the size based on the current rotation of the display. 490 Point p = new Point(); 491 mContext.getDisplay().getRealSize(p); 492 493 // Transform x, y to portrait mode if the device is in landscape mode. 494 switch (mContext.getDisplay().getRotation()) { 495 case Surface.ROTATION_90: 496 x = p.y - rawY; 497 y = rawX; 498 break; 499 500 case Surface.ROTATION_270: 501 x = rawY; 502 y = p.x - rawX; 503 break; 504 505 default: 506 // Do nothing to stay in portrait mode. 507 } 508 509 onFingerDown(x, y, minor, major); 510 Log.v(TAG, "onTouch | finger down: " + touchInfo); 511 mTouchLogTime = mSystemClock.elapsedRealtime(); 512 mPowerManager.userActivity(mSystemClock.uptimeMillis(), 513 PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0); 514 handled = true; 515 } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) { 516 Log.v(TAG, "onTouch | finger move: " + touchInfo); 517 mTouchLogTime = mSystemClock.elapsedRealtime(); 518 } 519 } else { 520 Log.v(TAG, "onTouch | finger outside"); 521 onFingerUp(); 522 } 523 } 524 Trace.endSection(); 525 break; 526 527 case MotionEvent.ACTION_UP: 528 case MotionEvent.ACTION_CANCEL: 529 case MotionEvent.ACTION_HOVER_EXIT: 530 Trace.beginSection("UdfpsController.onTouch.ACTION_UP"); 531 mActivePointerId = -1; 532 if (mVelocityTracker != null) { 533 mVelocityTracker.recycle(); 534 mVelocityTracker = null; 535 } 536 Log.v(TAG, "onTouch | finger up"); 537 mAttemptedToDismissKeyguard = false; 538 onFingerUp(); 539 mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); 540 Trace.endSection(); 541 break; 542 543 default: 544 // Do nothing. 545 } 546 return handled; 547 } 548 shouldTryToDismissKeyguard()549 private boolean shouldTryToDismissKeyguard() { 550 return mView.getAnimationViewController() != null 551 && mView.getAnimationViewController() instanceof UdfpsKeyguardViewController 552 && mKeyguardStateController.canDismissLockScreen() 553 && !mAttemptedToDismissKeyguard; 554 } 555 556 @Inject UdfpsController(@onNull Context context, @NonNull Execution execution, @NonNull LayoutInflater inflater, @Nullable FingerprintManager fingerprintManager, @NonNull WindowManager windowManager, @NonNull StatusBarStateController statusBarStateController, @Main DelayableExecutor fgExecutor, @NonNull PanelExpansionStateManager panelExpansionStateManager, @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, @NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController, @NonNull ScreenLifecycle screenLifecycle, @Nullable Vibrator vibrator, @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, @NonNull Optional<UdfpsHbmProvider> hbmProvider, @NonNull KeyguardStateController keyguardStateController, @NonNull KeyguardBypassController keyguardBypassController, @NonNull DisplayManager displayManager, @Main Handler mainHandler, @NonNull ConfigurationController configurationController, @NonNull SystemClock systemClock, @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @NonNull SystemUIDialogManager dialogManager)557 public UdfpsController(@NonNull Context context, 558 @NonNull Execution execution, 559 @NonNull LayoutInflater inflater, 560 @Nullable FingerprintManager fingerprintManager, 561 @NonNull WindowManager windowManager, 562 @NonNull StatusBarStateController statusBarStateController, 563 @Main DelayableExecutor fgExecutor, 564 @NonNull PanelExpansionStateManager panelExpansionStateManager, 565 @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, 566 @NonNull DumpManager dumpManager, 567 @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, 568 @NonNull FalsingManager falsingManager, 569 @NonNull PowerManager powerManager, 570 @NonNull AccessibilityManager accessibilityManager, 571 @NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController, 572 @NonNull ScreenLifecycle screenLifecycle, 573 @Nullable Vibrator vibrator, 574 @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, 575 @NonNull Optional<UdfpsHbmProvider> hbmProvider, 576 @NonNull KeyguardStateController keyguardStateController, 577 @NonNull KeyguardBypassController keyguardBypassController, 578 @NonNull DisplayManager displayManager, 579 @Main Handler mainHandler, 580 @NonNull ConfigurationController configurationController, 581 @NonNull SystemClock systemClock, 582 @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, 583 @NonNull SystemUIDialogManager dialogManager) { 584 mContext = context; 585 mExecution = execution; 586 mVibrator = vibrator; 587 mInflater = inflater; 588 // The fingerprint manager is queried for UDFPS before this class is constructed, so the 589 // fingerprint manager should never be null. 590 mFingerprintManager = checkNotNull(fingerprintManager); 591 mWindowManager = windowManager; 592 mFgExecutor = fgExecutor; 593 mPanelExpansionStateManager = panelExpansionStateManager; 594 mStatusBarStateController = statusBarStateController; 595 mKeyguardStateController = keyguardStateController; 596 mKeyguardViewManager = statusBarKeyguardViewManager; 597 mDumpManager = dumpManager; 598 mDialogManager = dialogManager; 599 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 600 mFalsingManager = falsingManager; 601 mPowerManager = powerManager; 602 mAccessibilityManager = accessibilityManager; 603 mLockscreenShadeTransitionController = lockscreenShadeTransitionController; 604 mHbmProvider = hbmProvider.orElse(null); 605 screenLifecycle.addObserver(mScreenObserver); 606 mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON; 607 mKeyguardBypassController = keyguardBypassController; 608 mConfigurationController = configurationController; 609 mSystemClock = systemClock; 610 mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; 611 612 mSensorProps = findFirstUdfps(); 613 // At least one UDFPS sensor exists 614 checkArgument(mSensorProps != null); 615 mOrientationListener = new BiometricDisplayListener( 616 context, 617 displayManager, 618 mainHandler, 619 new BiometricDisplayListener.SensorType.UnderDisplayFingerprint(mSensorProps), 620 () -> { 621 onOrientationChanged(); 622 return Unit.INSTANCE; 623 }); 624 625 mCoreLayoutParams = new WindowManager.LayoutParams( 626 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, 627 0 /* flags set in computeLayoutParams() */, 628 PixelFormat.TRANSLUCENT); 629 mCoreLayoutParams.setTitle(TAG); 630 mCoreLayoutParams.setFitInsetsTypes(0); 631 mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; 632 mCoreLayoutParams.layoutInDisplayCutoutMode = 633 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 634 mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 635 636 mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController()); 637 638 final IntentFilter filter = new IntentFilter(); 639 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 640 context.registerReceiver(mBroadcastReceiver, filter); 641 642 udfpsHapticsSimulator.setUdfpsController(this); 643 } 644 645 /** 646 * Play haptic to signal udfps scanning started. 647 */ 648 @VisibleForTesting playStartHaptic()649 public void playStartHaptic() { 650 if (mVibrator != null) { 651 mVibrator.vibrate( 652 Process.myUid(), 653 mContext.getOpPackageName(), 654 EFFECT_CLICK, 655 "udfps-onStart-click", 656 VIBRATION_SONIFICATION_ATTRIBUTES); 657 } 658 } 659 660 @Nullable findFirstUdfps()661 private FingerprintSensorPropertiesInternal findFirstUdfps() { 662 for (FingerprintSensorPropertiesInternal props : 663 mFingerprintManager.getSensorPropertiesInternal()) { 664 if (props.isAnyUdfpsType()) { 665 return props; 666 } 667 } 668 return null; 669 } 670 671 @Override dozeTimeTick()672 public void dozeTimeTick() { 673 if (mView != null) { 674 mView.dozeTimeTick(); 675 } 676 } 677 678 /** 679 * @return where the UDFPS exists on the screen in pixels. 680 */ getSensorLocation()681 public RectF getSensorLocation() { 682 // This is currently used to calculate the amount of space available for notifications 683 // on lockscreen and for the udfps light reveal animation on keyguard. 684 // Keyguard is only shown in portrait mode for now, so this will need to 685 // be updated if that ever changes. 686 final SensorLocationInternal location = mSensorProps.getLocation(); 687 return new RectF(location.sensorLocationX - location.sensorRadius, 688 location.sensorLocationY - location.sensorRadius, 689 location.sensorLocationX + location.sensorRadius, 690 location.sensorLocationY + location.sensorRadius); 691 } 692 updateOverlay()693 private void updateOverlay() { 694 mExecution.assertIsMainThread(); 695 696 if (mServerRequest != null) { 697 showUdfpsOverlay(mServerRequest); 698 } else { 699 hideUdfpsOverlay(); 700 } 701 } 702 shouldRotate(@ullable UdfpsAnimationViewController animation)703 private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) { 704 if (!(animation instanceof UdfpsKeyguardViewController)) { 705 // always rotate view if we're not on the keyguard 706 return true; 707 } 708 709 // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded 710 if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) { 711 return false; 712 } 713 714 return true; 715 } 716 computeLayoutParams( @ullable UdfpsAnimationViewController animation)717 private WindowManager.LayoutParams computeLayoutParams( 718 @Nullable UdfpsAnimationViewController animation) { 719 final int paddingX = animation != null ? animation.getPaddingX() : 0; 720 final int paddingY = animation != null ? animation.getPaddingY() : 0; 721 722 mCoreLayoutParams.flags = Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS 723 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 724 if (animation != null && animation.listenForTouchesOutsideView()) { 725 mCoreLayoutParams.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 726 } 727 728 // Default dimensions assume portrait mode. 729 final SensorLocationInternal location = mSensorProps.getLocation(); 730 mCoreLayoutParams.x = location.sensorLocationX - location.sensorRadius - paddingX; 731 mCoreLayoutParams.y = location.sensorLocationY - location.sensorRadius - paddingY; 732 mCoreLayoutParams.height = 2 * location.sensorRadius + 2 * paddingX; 733 mCoreLayoutParams.width = 2 * location.sensorRadius + 2 * paddingY; 734 735 Point p = new Point(); 736 // Gets the size based on the current rotation of the display. 737 mContext.getDisplay().getRealSize(p); 738 739 // Transform dimensions if the device is in landscape mode 740 switch (mContext.getDisplay().getRotation()) { 741 case Surface.ROTATION_90: 742 if (!shouldRotate(animation)) { 743 Log.v(TAG, "skip rotating udfps location ROTATION_90"); 744 break; 745 } else { 746 Log.v(TAG, "rotate udfps location ROTATION_90"); 747 } 748 mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius 749 - paddingX; 750 mCoreLayoutParams.y = p.y - location.sensorLocationX - location.sensorRadius 751 - paddingY; 752 break; 753 754 case Surface.ROTATION_270: 755 if (!shouldRotate(animation)) { 756 Log.v(TAG, "skip rotating udfps location ROTATION_270"); 757 break; 758 } else { 759 Log.v(TAG, "rotate udfps location ROTATION_270"); 760 } 761 mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius 762 - paddingX; 763 mCoreLayoutParams.y = location.sensorLocationX - location.sensorRadius 764 - paddingY; 765 break; 766 767 default: 768 // Do nothing to stay in portrait mode. 769 // Keyguard is always in portrait mode. 770 } 771 // avoid announcing window title 772 mCoreLayoutParams.accessibilityTitle = " "; 773 return mCoreLayoutParams; 774 } 775 776 onOrientationChanged()777 private void onOrientationChanged() { 778 // When the configuration changes it's almost always necessary to destroy and re-create 779 // the overlay's window to pass it the new LayoutParams. 780 // Hiding the overlay will destroy its window. It's safe to hide the overlay regardless 781 // of whether it is already hidden. 782 final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); 783 hideUdfpsOverlay(); 784 785 // If the overlay needs to be shown, this will re-create and show the overlay with the 786 // updated LayoutParams. Otherwise, the overlay will remain hidden. 787 updateOverlay(); 788 if (wasShowingAltAuth) { 789 mKeyguardViewManager.showGenericBouncer(true); 790 } 791 } 792 showUdfpsOverlay(@onNull ServerRequest request)793 private void showUdfpsOverlay(@NonNull ServerRequest request) { 794 mExecution.assertIsMainThread(); 795 796 final int reason = request.mRequestReason; 797 if (mView == null) { 798 try { 799 Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason); 800 801 mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false); 802 mOnFingerDown = false; 803 mView.setSensorProperties(mSensorProps); 804 mView.setHbmProvider(mHbmProvider); 805 UdfpsAnimationViewController<?> animation = inflateUdfpsAnimation(reason); 806 mAttemptedToDismissKeyguard = false; 807 if (animation != null) { 808 animation.init(); 809 mView.setAnimationViewController(animation); 810 } 811 mOrientationListener.enable(); 812 813 // This view overlaps the sensor area, so prevent it from being selectable 814 // during a11y. 815 if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR 816 || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING 817 || reason == BiometricOverlayConstants.REASON_AUTH_BP) { 818 mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 819 } 820 821 mWindowManager.addView(mView, computeLayoutParams(animation)); 822 mAccessibilityManager.addTouchExplorationStateChangeListener( 823 mTouchExplorationStateChangeListener); 824 updateTouchListener(); 825 } catch (RuntimeException e) { 826 Log.e(TAG, "showUdfpsOverlay | failed to add window", e); 827 } 828 } else { 829 Log.v(TAG, "showUdfpsOverlay | the overlay is already showing"); 830 } 831 } 832 833 @Nullable inflateUdfpsAnimation(int reason)834 private UdfpsAnimationViewController<?> inflateUdfpsAnimation(int reason) { 835 switch (reason) { 836 case BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR: 837 case BiometricOverlayConstants.REASON_ENROLL_ENROLLING: 838 UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate( 839 R.layout.udfps_enroll_view, null); 840 mView.addView(enrollView); 841 enrollView.updateSensorLocation(mSensorProps); 842 return new UdfpsEnrollViewController( 843 enrollView, 844 mServerRequest.mEnrollHelper, 845 mStatusBarStateController, 846 mPanelExpansionStateManager, 847 mDialogManager, 848 mDumpManager 849 ); 850 case BiometricOverlayConstants.REASON_AUTH_KEYGUARD: 851 UdfpsKeyguardView keyguardView = (UdfpsKeyguardView) 852 mInflater.inflate(R.layout.udfps_keyguard_view, null); 853 mView.addView(keyguardView); 854 return new UdfpsKeyguardViewController( 855 keyguardView, 856 mStatusBarStateController, 857 mPanelExpansionStateManager, 858 mKeyguardViewManager, 859 mKeyguardUpdateMonitor, 860 mDumpManager, 861 mLockscreenShadeTransitionController, 862 mConfigurationController, 863 mSystemClock, 864 mKeyguardStateController, 865 mUnlockedScreenOffAnimationController, 866 mDialogManager, 867 this 868 ); 869 case BiometricOverlayConstants.REASON_AUTH_BP: 870 // note: empty controller, currently shows no visual affordance 871 UdfpsBpView bpView = (UdfpsBpView) mInflater.inflate(R.layout.udfps_bp_view, null); 872 mView.addView(bpView); 873 return new UdfpsBpViewController( 874 bpView, 875 mStatusBarStateController, 876 mPanelExpansionStateManager, 877 mDialogManager, 878 mDumpManager 879 ); 880 case BiometricOverlayConstants.REASON_AUTH_OTHER: 881 case BiometricOverlayConstants.REASON_AUTH_SETTINGS: 882 UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView) 883 mInflater.inflate(R.layout.udfps_fpm_other_view, null); 884 mView.addView(authOtherView); 885 return new UdfpsFpmOtherViewController( 886 authOtherView, 887 mStatusBarStateController, 888 mPanelExpansionStateManager, 889 mDialogManager, 890 mDumpManager 891 ); 892 default: 893 Log.e(TAG, "Animation for reason " + reason + " not supported yet"); 894 return null; 895 } 896 } 897 hideUdfpsOverlay()898 private void hideUdfpsOverlay() { 899 mExecution.assertIsMainThread(); 900 901 if (mView != null) { 902 Log.v(TAG, "hideUdfpsOverlay | removing window"); 903 // Reset the controller back to its starting state. 904 onFingerUp(); 905 boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); 906 mWindowManager.removeView(mView); 907 mView.setOnTouchListener(null); 908 mView.setOnHoverListener(null); 909 mView.setAnimationViewController(null); 910 if (wasShowingAltAuth) { 911 mKeyguardViewManager.resetAlternateAuth(true); 912 } 913 mAccessibilityManager.removeTouchExplorationStateChangeListener( 914 mTouchExplorationStateChangeListener); 915 mView = null; 916 } else { 917 Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden"); 918 } 919 920 mOrientationListener.disable(); 921 } 922 923 /** 924 * Request fingerprint scan. 925 * 926 * This is intended to be called in response to a sensor that triggers an AOD interrupt for the 927 * fingerprint sensor. 928 */ onAodInterrupt(int screenX, int screenY, float major, float minor)929 void onAodInterrupt(int screenX, int screenY, float major, float minor) { 930 if (mIsAodInterruptActive) { 931 return; 932 } 933 934 if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { 935 mKeyguardViewManager.showBouncer(true); 936 return; 937 } 938 939 mAodInterruptRunnable = () -> { 940 mIsAodInterruptActive = true; 941 // Since the sensor that triggers the AOD interrupt doesn't provide 942 // ACTION_UP/ACTION_CANCEL, we need to be careful about not letting the screen 943 // accidentally remain in high brightness mode. As a mitigation, queue a call to 944 // cancel the fingerprint scan. 945 mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps, 946 AOD_INTERRUPT_TIMEOUT_MILLIS); 947 // using a hard-coded value for major and minor until it is available from the sensor 948 onFingerDown(screenX, screenY, minor, major); 949 }; 950 951 if (mScreenOn && mAodInterruptRunnable != null) { 952 mAodInterruptRunnable.run(); 953 mAodInterruptRunnable = null; 954 } 955 } 956 957 /** 958 * Add a callback for fingerUp and fingerDown events 959 */ addCallback(Callback cb)960 public void addCallback(Callback cb) { 961 mCallbacks.add(cb); 962 } 963 964 /** 965 * Remove callback 966 */ removeCallback(Callback cb)967 public void removeCallback(Callback cb) { 968 mCallbacks.remove(cb); 969 } 970 971 /** 972 * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before 973 * user explicitly lifts their finger. Generally, this should be called whenever udfps fails 974 * or errors. 975 * 976 * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give 977 * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually. 978 * This should be called when authentication either succeeds or fails. Failing to cancel the 979 * scan will leave the screen in high brightness mode and will show the HbmSurfaceView until 980 * the user lifts their finger. 981 */ onCancelUdfps()982 void onCancelUdfps() { 983 onFingerUp(); 984 if (!mIsAodInterruptActive) { 985 return; 986 } 987 if (mCancelAodTimeoutAction != null) { 988 mCancelAodTimeoutAction.run(); 989 mCancelAodTimeoutAction = null; 990 } 991 mIsAodInterruptActive = false; 992 } 993 isFingerDown()994 public boolean isFingerDown() { 995 return mOnFingerDown; 996 } 997 onFingerDown(int x, int y, float minor, float major)998 private void onFingerDown(int x, int y, float minor, float major) { 999 mExecution.assertIsMainThread(); 1000 if (mView == null) { 1001 Log.w(TAG, "Null view in onFingerDown"); 1002 return; 1003 } 1004 1005 if (mView.getAnimationViewController() instanceof UdfpsKeyguardViewController 1006 && !mStatusBarStateController.isDozing()) { 1007 mKeyguardBypassController.setUserHasDeviceEntryIntent(true); 1008 } 1009 1010 if (!mOnFingerDown) { 1011 playStartHaptic(); 1012 1013 if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) { 1014 mKeyguardUpdateMonitor.requestFaceAuth(/* userInitiatedRequest */ false); 1015 } 1016 } 1017 mOnFingerDown = true; 1018 mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); 1019 Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); 1020 Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0); 1021 mView.startIllumination(() -> { 1022 mFingerprintManager.onUiReady(mSensorProps.sensorId); 1023 Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); 1024 }); 1025 1026 for (Callback cb : mCallbacks) { 1027 cb.onFingerDown(); 1028 } 1029 } 1030 onFingerUp()1031 private void onFingerUp() { 1032 mExecution.assertIsMainThread(); 1033 mActivePointerId = -1; 1034 mGoodCaptureReceived = false; 1035 if (mView == null) { 1036 Log.w(TAG, "Null view in onFingerUp"); 1037 return; 1038 } 1039 if (mOnFingerDown) { 1040 mFingerprintManager.onPointerUp(mSensorProps.sensorId); 1041 for (Callback cb : mCallbacks) { 1042 cb.onFingerUp(); 1043 } 1044 } 1045 mOnFingerDown = false; 1046 if (mView.isIlluminationRequested()) { 1047 mView.stopIllumination(); 1048 } 1049 } 1050 updateTouchListener()1051 private void updateTouchListener() { 1052 if (mView == null) { 1053 return; 1054 } 1055 1056 if (mAccessibilityManager.isTouchExplorationEnabled()) { 1057 mView.setOnHoverListener(mOnHoverListener); 1058 mView.setOnTouchListener(null); 1059 } else { 1060 mView.setOnHoverListener(null); 1061 mView.setOnTouchListener(mOnTouchListener); 1062 } 1063 } 1064 1065 /** 1066 * Callback for fingerUp and fingerDown events. 1067 */ 1068 public interface Callback { 1069 /** 1070 * Called onFingerUp events. Will only be called if the finger was previously down. 1071 */ onFingerUp()1072 void onFingerUp(); 1073 1074 /** 1075 * Called onFingerDown events. 1076 */ onFingerDown()1077 void onFingerDown(); 1078 } 1079 } 1080