• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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