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