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