1 /*
2  * Copyright (C) 2019 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.BiometricAuthenticator.TYPE_FINGERPRINT;
20 import static android.hardware.biometrics.BiometricManager.Authenticators;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static junit.framework.Assert.assertEquals;
25 import static junit.framework.Assert.assertFalse;
26 import static junit.framework.Assert.assertNotSame;
27 import static junit.framework.Assert.assertNull;
28 import static junit.framework.Assert.assertTrue;
29 
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyInt;
32 import static org.mockito.ArgumentMatchers.anyLong;
33 import static org.mockito.ArgumentMatchers.anyString;
34 import static org.mockito.ArgumentMatchers.eq;
35 import static org.mockito.Mockito.doAnswer;
36 import static org.mockito.Mockito.doReturn;
37 import static org.mockito.Mockito.mock;
38 import static org.mockito.Mockito.never;
39 import static org.mockito.Mockito.reset;
40 import static org.mockito.Mockito.same;
41 import static org.mockito.Mockito.spy;
42 import static org.mockito.Mockito.verify;
43 import static org.mockito.Mockito.when;
44 
45 import android.app.ActivityManager;
46 import android.app.ActivityTaskManager;
47 import android.content.ComponentName;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.content.pm.PackageManager;
51 import android.content.res.Configuration;
52 import android.content.res.Resources;
53 import android.graphics.Point;
54 import android.hardware.biometrics.BiometricAuthenticator;
55 import android.hardware.biometrics.BiometricConstants;
56 import android.hardware.biometrics.BiometricPrompt;
57 import android.hardware.biometrics.BiometricStateListener;
58 import android.hardware.biometrics.ComponentInfoInternal;
59 import android.hardware.biometrics.IBiometricContextListener;
60 import android.hardware.biometrics.IBiometricSysuiReceiver;
61 import android.hardware.biometrics.PromptInfo;
62 import android.hardware.biometrics.SensorProperties;
63 import android.hardware.display.DisplayManager;
64 import android.hardware.face.FaceManager;
65 import android.hardware.face.FaceSensorProperties;
66 import android.hardware.face.FaceSensorPropertiesInternal;
67 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
68 import android.hardware.fingerprint.FingerprintManager;
69 import android.hardware.fingerprint.FingerprintSensorProperties;
70 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
71 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
72 import android.os.Bundle;
73 import android.os.Handler;
74 import android.os.RemoteException;
75 import android.os.UserManager;
76 import android.testing.TestableContext;
77 import android.testing.TestableLooper;
78 import android.testing.TestableLooper.RunWithLooper;
79 import android.view.DisplayInfo;
80 import android.view.Surface;
81 import android.view.WindowManager;
82 
83 import androidx.test.ext.junit.runners.AndroidJUnit4;
84 import androidx.test.filters.SmallTest;
85 
86 import com.android.internal.R;
87 import com.android.internal.jank.InteractionJankMonitor;
88 import com.android.internal.widget.LockPatternUtils;
89 import com.android.settingslib.udfps.UdfpsUtils;
90 import com.android.systemui.RoboPilotTest;
91 import com.android.systemui.SysuiTestCase;
92 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
93 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
94 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
95 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
96 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
97 import com.android.systemui.flags.FakeFeatureFlags;
98 import com.android.systemui.flags.Flags;
99 import com.android.systemui.keyguard.WakefulnessLifecycle;
100 import com.android.systemui.statusbar.CommandQueue;
101 import com.android.systemui.statusbar.VibratorHelper;
102 import com.android.systemui.util.concurrency.DelayableExecutor;
103 import com.android.systemui.util.concurrency.Execution;
104 import com.android.systemui.util.concurrency.FakeExecution;
105 import com.android.systemui.util.concurrency.FakeExecutor;
106 import com.android.systemui.util.time.FakeSystemClock;
107 
108 import org.junit.Before;
109 import org.junit.Rule;
110 import org.junit.Test;
111 import org.junit.runner.RunWith;
112 import org.mockito.AdditionalMatchers;
113 import org.mockito.ArgumentCaptor;
114 import org.mockito.Captor;
115 import org.mockito.Mock;
116 import org.mockito.junit.MockitoJUnit;
117 import org.mockito.junit.MockitoRule;
118 
119 import java.util.ArrayList;
120 import java.util.List;
121 import java.util.Random;
122 
123 @RunWith(AndroidJUnit4.class)
124 @RunWithLooper
125 @SmallTest
126 @RoboPilotTest
127 public class AuthControllerTest extends SysuiTestCase {
128 
129     private static final long REQUEST_ID = 22;
130 
131     @Rule
132     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
133 
134     @Mock
135     private PackageManager mPackageManager;
136     @Mock
137     private IBiometricSysuiReceiver mReceiver;
138     @Mock
139     private IBiometricContextListener mContextListener;
140     @Mock
141     private AuthDialog mDialog1;
142     @Mock
143     private AuthDialog mDialog2;
144     @Mock
145     private CommandQueue mCommandQueue;
146     @Mock
147     private ActivityTaskManager mActivityTaskManager;
148     @Mock
149     private WindowManager mWindowManager;
150     @Mock
151     private FingerprintManager mFingerprintManager;
152     @Mock
153     private FaceManager mFaceManager;
154     @Mock
155     private UdfpsController mUdfpsController;
156     @Mock
157     private SideFpsController mSideFpsController;
158     @Mock
159     private DisplayManager mDisplayManager;
160     @Mock
161     private WakefulnessLifecycle mWakefulnessLifecycle;
162     @Mock
163     private AuthDialogPanelInteractionDetector mPanelInteractionDetector;
164     @Mock
165     private UserManager mUserManager;
166     @Mock
167     private LockPatternUtils mLockPatternUtils;
168     @Mock
169     private LogContextInteractor mLogContextInteractor;
170     @Mock
171     private UdfpsLogger mUdfpsLogger;
172     @Mock
173     private InteractionJankMonitor mInteractionJankMonitor;
174     @Mock
175     private PromptCredentialInteractor mBiometricPromptCredentialInteractor;
176     @Mock
177     private PromptSelectorInteractor mPromptSelectionInteractor;
178     @Mock
179     private CredentialViewModel mCredentialViewModel;
180     @Mock
181     private PromptViewModel mPromptViewModel;
182     @Mock
183     private UdfpsUtils mUdfpsUtils;
184 
185     @Captor
186     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
187     @Captor
188     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mFaceAuthenticatorsRegisteredCaptor;
189     @Captor
190     private ArgumentCaptor<BiometricStateListener> mBiometricStateCaptor;
191     @Captor
192     private ArgumentCaptor<Integer> mModalityCaptor;
193     @Captor
194     private ArgumentCaptor<String> mMessageCaptor;
195     @Mock
196     private Resources mResources;
197     @Mock
198     private VibratorHelper mVibratorHelper;
199 
200     private TestableContext mContextSpy;
201     private Execution mExecution;
202     private TestableLooper mTestableLooper;
203     private Handler mHandler;
204     private DelayableExecutor mBackgroundExecutor;
205     private TestableAuthController mAuthController;
206     private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
207 
208     @Before
setup()209     public void setup() throws RemoteException {
210         // TODO(b/278622168): remove with flag
211         // AuthController simply passes this through to AuthContainerView (does not impact test)
212         mFeatureFlags.set(Flags.BIOMETRIC_BP_STRONG, false);
213 
214         mContextSpy = spy(mContext);
215         mExecution = new FakeExecution();
216         mTestableLooper = TestableLooper.get(this);
217         mHandler = new Handler(mTestableLooper.getLooper());
218         mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
219 
220         when(mContextSpy.getPackageManager()).thenReturn(mPackageManager);
221         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE))
222                 .thenReturn(true);
223         when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT))
224                 .thenReturn(true);
225 
226         when(mDialog1.getOpPackageName()).thenReturn("Dialog1");
227         when(mDialog2.getOpPackageName()).thenReturn("Dialog2");
228 
229         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
230         when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
231 
232         when(mDialog1.getRequestId()).thenReturn(REQUEST_ID);
233         when(mDialog2.getRequestId()).thenReturn(REQUEST_ID);
234 
235         when(mDisplayManager.getStableDisplaySize()).thenReturn(new Point());
236 
237         when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
238         when(mFaceManager.isHardwareDetected()).thenReturn(true);
239 
240         final List<ComponentInfoInternal> fpComponentInfo = List.of(
241                 new ComponentInfoInternal("faceSensor" /* componentId */,
242                         "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
243                         "00000001" /* serialNumber */, "" /* softwareVersion */));
244         final List<ComponentInfoInternal> faceComponentInfo = List.of(
245                 new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
246                         "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
247                         "vendor/version/revision" /* softwareVersion */));
248 
249         final List<FingerprintSensorPropertiesInternal> fpProps = List.of(
250                 new FingerprintSensorPropertiesInternal(
251                         1 /* sensorId */,
252                         SensorProperties.STRENGTH_STRONG,
253                         1 /* maxEnrollmentsPerUser */,
254                         fpComponentInfo,
255                         FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
256                         true /* resetLockoutRequireHardwareAuthToken */));
257         when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(fpProps);
258 
259         final List<FaceSensorPropertiesInternal> faceProps = List.of(
260                 new FaceSensorPropertiesInternal(
261                         2 /* sensorId */,
262                         SensorProperties.STRENGTH_STRONG,
263                         1 /* maxEnrollmentsPerUser */,
264                         faceComponentInfo,
265                         FaceSensorProperties.TYPE_RGB,
266                         true /* supportsFaceDetection */,
267                         true /* supportsSelfIllumination */,
268                         true /* resetLockoutRequireHardwareAuthToken */));
269         when(mFaceManager.getSensorPropertiesInternal()).thenReturn(faceProps);
270 
271         mAuthController = new TestableAuthController(mContextSpy);
272 
273         mAuthController.start();
274         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
275                 mFpAuthenticatorsRegisteredCaptor.capture());
276         verify(mFaceManager).addAuthenticatorsRegisteredCallback(
277                 mFaceAuthenticatorsRegisteredCaptor.capture());
278 
279         mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(fpProps);
280         mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps);
281 
282         // Ensures that the operations posted on the handler get executed.
283         waitForIdleSync();
284     }
285 
286     // Callback tests
287 
288     @Test
testRegistersBiometricStateListener_afterAllAuthenticatorsAreRegistered()289     public void testRegistersBiometricStateListener_afterAllAuthenticatorsAreRegistered()
290             throws RemoteException {
291         // This test is sensitive to prior FingerprintManager interactions.
292         reset(mFingerprintManager);
293         reset(mFaceManager);
294 
295         // This test requires an uninitialized AuthController.
296         AuthController authController = new TestableAuthController(mContextSpy);
297         authController.start();
298 
299         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
300                 mFpAuthenticatorsRegisteredCaptor.capture());
301         verify(mFaceManager).addAuthenticatorsRegisteredCallback(
302                 mFaceAuthenticatorsRegisteredCaptor.capture());
303         waitForIdleSync();
304 
305         verify(mFingerprintManager, never()).registerBiometricStateListener(any());
306         verify(mFaceManager, never()).registerBiometricStateListener(any());
307 
308         mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
309         mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
310         waitForIdleSync();
311 
312         verify(mFingerprintManager).registerBiometricStateListener(any());
313         verify(mFaceManager).registerBiometricStateListener(any());
314     }
315 
316     @Test
testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor()317     public void testDoesNotCrash_afterEnrollmentsChangedForUnknownSensor() throws RemoteException {
318         // This test is sensitive to prior FingerprintManager interactions.
319         reset(mFingerprintManager);
320         reset(mFaceManager);
321 
322         // This test requires an uninitialized AuthController.
323         AuthController authController = new TestableAuthController(mContextSpy);
324         authController.start();
325 
326         verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
327                 mFpAuthenticatorsRegisteredCaptor.capture());
328         verify(mFaceManager).addAuthenticatorsRegisteredCallback(
329                 mFaceAuthenticatorsRegisteredCaptor.capture());
330 
331         // Emulates a device with no authenticators (empty list).
332         mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
333         mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of());
334         waitForIdleSync();
335 
336         verify(mFingerprintManager).registerBiometricStateListener(
337                 mBiometricStateCaptor.capture());
338         verify(mFaceManager).registerBiometricStateListener(
339                 mBiometricStateCaptor.capture());
340 
341         // Enrollments changed for an unknown sensor.
342         for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
343             listener.onEnrollmentsChanged(0 /* userId */,
344                     0xbeef /* sensorId */, true /* hasEnrollments */);
345         }
346         waitForIdleSync();
347 
348         // Nothing should crash.
349     }
350 
351     @Test
testFaceAuthEnrollmentStatus()352     public void testFaceAuthEnrollmentStatus() throws RemoteException {
353         final int userId = 0;
354 
355         reset(mFaceManager);
356         mAuthController.start();
357 
358         verify(mFaceManager).addAuthenticatorsRegisteredCallback(
359                 mFaceAuthenticatorsRegisteredCaptor.capture());
360 
361         mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(
362                 mFaceManager.getSensorPropertiesInternal());
363         mTestableLooper.processAllMessages();
364 
365         verify(mFaceManager).registerBiometricStateListener(
366                 mBiometricStateCaptor.capture());
367 
368         assertFalse(mAuthController.isFaceAuthEnrolled(userId));
369 
370         // Enrollments changed for an unknown sensor.
371         for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
372             listener.onEnrollmentsChanged(userId,
373                     2 /* sensorId */, true /* hasEnrollments */);
374         }
375         mTestableLooper.processAllMessages();
376 
377         assertTrue(mAuthController.isFaceAuthEnrolled(userId));
378     }
379 
380 
381     @Test
testSendsReasonUserCanceled_whenDismissedByUserCancel()382     public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
383         showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
384         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
385                 null, /* credentialAttestation */
386                 mAuthController.mCurrentDialog.getRequestId());
387         verify(mReceiver).onDialogDismissed(
388                 eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
389                 eq(null) /* credentialAttestation */);
390     }
391 
392     @Test
testSendsReasonNegative_whenDismissedByButtonNegative()393     public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
394         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
395         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE,
396                 null, /* credentialAttestation */
397                 mAuthController.mCurrentDialog.getRequestId());
398         verify(mReceiver).onDialogDismissed(
399                 eq(BiometricPrompt.DISMISSED_REASON_NEGATIVE),
400                 eq(null) /* credentialAttestation */);
401     }
402 
403     @Test
testSendsReasonConfirmed_whenDismissedByButtonPositive()404     public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
405         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
406         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE,
407                 null, /* credentialAttestation */
408                 mAuthController.mCurrentDialog.getRequestId());
409         verify(mReceiver).onDialogDismissed(
410                 eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
411                 eq(null) /* credentialAttestation */);
412     }
413 
414     @Test
testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated()415     public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
416         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
417         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED,
418                 null, /* credentialAttestation */
419                 mAuthController.mCurrentDialog.getRequestId());
420         verify(mReceiver).onDialogDismissed(
421                 eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
422                 eq(null) /* credentialAttestation */);
423     }
424 
425     @Test
testSendsReasonError_whenDismissedByError()426     public void testSendsReasonError_whenDismissedByError() throws Exception {
427         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
428         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR,
429                 null, /* credentialAttestation */
430                 mAuthController.mCurrentDialog.getRequestId());
431         verify(mReceiver).onDialogDismissed(
432                 eq(BiometricPrompt.DISMISSED_REASON_ERROR),
433                 eq(null) /* credentialAttestation */);
434     }
435 
436     @Test
testSendsReasonServerRequested_whenDismissedByServer()437     public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
438         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
439         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER,
440                 null, /* credentialAttestation */
441                 mAuthController.mCurrentDialog.getRequestId());
442         verify(mReceiver).onDialogDismissed(
443                 eq(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED),
444                 eq(null) /* credentialAttestation */);
445     }
446 
447     @Test
testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()448     public void testSendsReasonCredentialConfirmed_whenDeviceCredentialAuthenticated()
449             throws Exception {
450         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
451 
452         final byte[] credentialAttestation = generateRandomHAT();
453 
454         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
455                 credentialAttestation, mAuthController.mCurrentDialog.getRequestId());
456         verify(mReceiver).onDialogDismissed(
457                 eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
458                 AdditionalMatchers.aryEq(credentialAttestation));
459     }
460 
461     // Statusbar tests
462 
463     @Test
testShowInvoked_whenSystemRequested()464     public void testShowInvoked_whenSystemRequested() {
465         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
466         verify(mDialog1).show(any(), any());
467     }
468 
469     @Test
testOnAuthenticationSucceededInvoked_whenSystemRequested()470     public void testOnAuthenticationSucceededInvoked_whenSystemRequested() {
471         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
472         mAuthController.onBiometricAuthenticated(TYPE_FINGERPRINT);
473         verify(mDialog1).onAuthenticationSucceeded(eq(TYPE_FINGERPRINT));
474     }
475 
476     @Test
testOnAuthenticationFailedInvoked_whenBiometricRejected()477     public void testOnAuthenticationFailedInvoked_whenBiometricRejected() {
478         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
479         final int modality = BiometricAuthenticator.TYPE_NONE;
480         mAuthController.onBiometricError(modality,
481                 BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
482                 0 /* vendorCode */);
483 
484         verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
485 
486         assertEquals(mModalityCaptor.getValue().intValue(), modality);
487         assertEquals(mMessageCaptor.getValue(),
488                 mContext.getString(R.string.biometric_not_recognized));
489     }
490 
491     @Test
testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused()492     public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withPaused() {
493         testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
494                 BiometricConstants.BIOMETRIC_PAUSED_REJECTED);
495     }
496 
497     @Test
testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withTimeout()498     public void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected_withTimeout() {
499         testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(
500                 BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
501     }
502 
testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(int error)503     private void testOnAuthenticationFailedInvoked_coex_whenFaceAuthRejected(int error) {
504         final int modality = BiometricAuthenticator.TYPE_FACE;
505         final int userId = 0;
506 
507         enrollFingerprintAndFace(userId);
508 
509         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
510 
511         mAuthController.onBiometricError(modality, error, 0 /* vendorCode */);
512 
513         verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
514 
515         assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
516         assertThat(mMessageCaptor.getValue()).isEqualTo(
517                 mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead));
518     }
519 
520     @Test
testOnAuthenticationFailedInvoked_whenFingerprintAuthRejected()521     public void testOnAuthenticationFailedInvoked_whenFingerprintAuthRejected() {
522         final int modality = BiometricAuthenticator.TYPE_FINGERPRINT;
523         final int userId = 0;
524 
525         enrollFingerprintAndFace(userId);
526 
527         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
528 
529         mAuthController.onBiometricError(modality,
530                 BiometricConstants.BIOMETRIC_PAUSED_REJECTED,
531                 0 /* vendorCode */);
532 
533         verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
534 
535         assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
536         assertThat(mMessageCaptor.getValue()).isEqualTo(
537                 mContext.getString(R.string.fingerprint_error_not_match));
538     }
539 
540     @Test
testOnAuthenticationFailedInvoked_whenBiometricTimedOut()541     public void testOnAuthenticationFailedInvoked_whenBiometricTimedOut() {
542         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
543         final int modality = BiometricAuthenticator.TYPE_FACE;
544         final int error = BiometricConstants.BIOMETRIC_ERROR_TIMEOUT;
545         final int vendorCode = 0;
546         mAuthController.onBiometricError(modality, error, vendorCode);
547 
548         verify(mDialog1).onAuthenticationFailed(mModalityCaptor.capture(), mMessageCaptor.capture());
549 
550         assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
551         assertThat(mMessageCaptor.getValue()).isEqualTo(
552                 mContext.getString(R.string.biometric_not_recognized));
553     }
554 
555     @Test
testOnHelpInvoked_whenSystemRequested()556     public void testOnHelpInvoked_whenSystemRequested() {
557         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
558         final int modality = BiometricAuthenticator.TYPE_IRIS;
559         final String helpMessage = "help";
560         mAuthController.onBiometricHelp(modality, helpMessage);
561 
562         verify(mDialog1).onHelp(mModalityCaptor.capture(), mMessageCaptor.capture());
563 
564         assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
565         assertThat(mMessageCaptor.getValue()).isEqualTo(helpMessage);
566     }
567 
568     @Test
testOnErrorInvoked_whenSystemRequested()569     public void testOnErrorInvoked_whenSystemRequested() {
570         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
571         final int modality = BiometricAuthenticator.TYPE_FACE;
572         final int error = 1;
573         final int vendorCode = 0;
574         mAuthController.onBiometricError(modality, error, vendorCode);
575 
576         verify(mDialog1).onError(mModalityCaptor.capture(), mMessageCaptor.capture());
577 
578         assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
579         assertThat(mMessageCaptor.getValue()).isEqualTo(
580                 FaceManager.getErrorString(mContext, error, vendorCode));
581     }
582 
583     @Test
testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI()584     public void testErrorLockout_whenCredentialAllowed_AnimatesToCredentialUI() {
585         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
586         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
587         final int vendorCode = 0;
588 
589         when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
590 
591         mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
592         verify(mDialog1, never()).onError(anyInt(), anyString());
593         verify(mDialog1).animateToCredentialUI(eq(true));
594     }
595 
596     @Test
testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI()597     public void testErrorLockoutPermanent_whenCredentialAllowed_AnimatesToCredentialUI() {
598         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
599         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
600         final int vendorCode = 0;
601 
602         when(mDialog1.isAllowDeviceCredentials()).thenReturn(true);
603 
604         mAuthController.onBiometricError(BiometricAuthenticator.TYPE_FACE, error, vendorCode);
605         verify(mDialog1, never()).onError(anyInt(), anyString());
606         verify(mDialog1).animateToCredentialUI(eq(true));
607     }
608 
609     @Test
testErrorLockout_whenCredentialNotAllowed_sendsOnError()610     public void testErrorLockout_whenCredentialNotAllowed_sendsOnError() {
611         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
612         final int modality = BiometricAuthenticator.TYPE_FACE;
613         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
614         final int vendorCode = 0;
615 
616         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
617 
618         mAuthController.onBiometricError(modality, error, vendorCode);
619         verify(mDialog1).onError(
620                 eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
621         verify(mDialog1, never()).animateToCredentialUI(eq(true));
622     }
623 
624     @Test
testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError()625     public void testErrorLockoutPermanent_whenCredentialNotAllowed_sendsOnError() {
626         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
627         final int modality = BiometricAuthenticator.TYPE_FACE;
628         final int error = BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
629         final int vendorCode = 0;
630 
631         when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
632 
633         mAuthController.onBiometricError(modality, error, vendorCode);
634         verify(mDialog1).onError(
635                 eq(modality), eq(FaceManager.getErrorString(mContext, error, vendorCode)));
636         verify(mDialog1, never()).animateToCredentialUI(eq(true));
637     }
638 
639     @Test
testHideAuthenticationDialog_invokesDismissFromSystemServer()640     public void testHideAuthenticationDialog_invokesDismissFromSystemServer() {
641         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
642 
643         mAuthController.hideAuthenticationDialog(REQUEST_ID + 1);
644         verify(mDialog1, never()).dismissFromSystemServer();
645         assertThat(mAuthController.mCurrentDialog).isSameInstanceAs(mDialog1);
646 
647         mAuthController.hideAuthenticationDialog(REQUEST_ID);
648         verify(mDialog1).dismissFromSystemServer();
649 
650         // In this case, BiometricService sends the error to the client immediately, without
651         // doing a round trip to SystemUI.
652         assertNull(mAuthController.mCurrentDialog);
653     }
654 
655     // Corner case tests
656 
657     @Test
testCancelAuthentication_whenCredentialConfirmed_doesntCrash()658     public void testCancelAuthentication_whenCredentialConfirmed_doesntCrash() throws Exception {
659         // It's possible that before the client is notified that credential is confirmed, the client
660         // requests to cancel authentication.
661         //
662         // Test that the following sequence of events does not crash SystemUI:
663         // 1) Credential is confirmed
664         // 2) Client cancels authentication
665 
666         showDialog(new int[0] /* sensorIds */, true /* credentialAllowed */);
667         verify(mDialog1).show(any(), any());
668 
669         final byte[] credentialAttestation = generateRandomHAT();
670 
671         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
672                 credentialAttestation, mAuthController.mCurrentDialog.getRequestId());
673         verify(mReceiver).onDialogDismissed(
674                 eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
675                 AdditionalMatchers.aryEq(credentialAttestation));
676 
677         mAuthController.hideAuthenticationDialog(REQUEST_ID);
678     }
679 
680     @Test
testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations()681     public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
682         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
683         verify(mDialog1).show(any(), any());
684 
685         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
686 
687         // First dialog should be dismissed without animation
688         verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
689 
690         // Second dialog should be shown without animation
691         verify(mDialog2).show(any(), any());
692     }
693 
694     @Test
testConfigurationPersists_whenOnConfigurationChanged()695     public void testConfigurationPersists_whenOnConfigurationChanged() {
696         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
697         verify(mDialog1).show(any(), any());
698 
699         // Return that the UI is in "showing" state
700         doAnswer(invocation -> {
701             Object[] args = invocation.getArguments();
702             Bundle savedState = (Bundle) args[0];
703             savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
704             return null; // onSaveState returns void
705         }).when(mDialog1).onSaveState(any());
706 
707         mAuthController.onConfigurationChanged(new Configuration());
708 
709         ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
710         verify(mDialog1).onSaveState(captor.capture());
711 
712         // Old dialog doesn't animate
713         verify(mDialog1).dismissWithoutCallback(eq(false /* animate */));
714 
715         // Saved state is restored into new dialog
716         ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
717         verify(mDialog2).show(any(), captor2.capture());
718 
719         // TODO: This should check all values we want to save/restore
720         assertEquals(captor.getValue(), captor2.getValue());
721     }
722 
723     @Test
testConfigurationPersists_whenBiometricFallbackToCredential()724     public void testConfigurationPersists_whenBiometricFallbackToCredential() {
725         showDialog(new int[] {1} /* sensorIds */, true /* credentialAllowed */);
726         verify(mDialog1).show(any(), any());
727 
728         // Pretend that the UI is now showing device credential UI.
729         doAnswer(invocation -> {
730             Object[] args = invocation.getArguments();
731             Bundle savedState = (Bundle) args[0];
732             savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
733             savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true);
734             return null; // onSaveState returns void
735         }).when(mDialog1).onSaveState(any());
736 
737         mAuthController.onConfigurationChanged(new Configuration());
738 
739         // Check that the new dialog was initialized to the credential UI.
740         ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
741         verify(mDialog2).show(any(), captor.capture());
742         assertEquals(Authenticators.DEVICE_CREDENTIAL,
743                 mAuthController.mLastBiometricPromptInfo.getAuthenticators());
744     }
745 
746     @Test
testClientNotified_whenTaskStackChangesDuringShow()747     public void testClientNotified_whenTaskStackChangesDuringShow() throws Exception {
748         switchTask("other_package");
749         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
750 
751         waitForIdleSync();
752 
753         assertNull(mAuthController.mCurrentDialog);
754         assertNull(mAuthController.mReceiver);
755         verify(mDialog1).dismissWithoutCallback(true /* animate */);
756         verify(mReceiver).onDialogDismissed(
757                 eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
758                 eq(null) /* credentialAttestation */);
759     }
760 
761     @Test
testClientNotified_whenTaskStackChangesDuringAuthentication()762     public void testClientNotified_whenTaskStackChangesDuringAuthentication() throws Exception {
763         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
764 
765         switchTask("other_package");
766 
767         mAuthController.mTaskStackListener.onTaskStackChanged();
768         waitForIdleSync();
769 
770         assertNull(mAuthController.mCurrentDialog);
771         assertNull(mAuthController.mReceiver);
772         verify(mDialog1).dismissWithoutCallback(true /* animate */);
773         verify(mReceiver).onDialogDismissed(
774                 eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
775                 eq(null) /* credentialAttestation */);
776     }
777 
778     @Test
testDoesNotCrash_whenTryAgainPressedAfterDismissal()779     public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
780         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
781         final long requestID = mAuthController.mCurrentDialog.getRequestId();
782         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
783                 null, /* credentialAttestation */requestID);
784         mAuthController.onTryAgainPressed(requestID);
785     }
786 
787     @Test
testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal()788     public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
789         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
790         final long requestID = mAuthController.mCurrentDialog.getRequestId();
791         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
792                 null /* credentialAttestation */, requestID);
793         mAuthController.onDeviceCredentialPressed(requestID);
794     }
795 
796     @Test
testActionCloseSystemDialogs_dismissesDialogIfShowing()797     public void testActionCloseSystemDialogs_dismissesDialogIfShowing() throws Exception {
798         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
799         Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
800         mAuthController.mBroadcastReceiver.onReceive(mContext, intent);
801         waitForIdleSync();
802 
803         assertNull(mAuthController.mCurrentDialog);
804         assertNull(mAuthController.mReceiver);
805         verify(mDialog1).dismissWithoutCallback(true /* animate */);
806         verify(mReceiver).onDialogDismissed(
807                 eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
808                 eq(null) /* credentialAttestation */);
809     }
810 
811     @Test
testOnAodInterrupt()812     public void testOnAodInterrupt() {
813         final int pos = 10;
814         final float majorMinor = 5f;
815         mAuthController.onAodInterrupt(pos, pos, majorMinor, majorMinor);
816         verify(mUdfpsController).onAodInterrupt(eq(pos), eq(pos), eq(majorMinor), eq(majorMinor));
817     }
818 
819     @Test
testSubscribesToOrientationChangesOnStart()820     public void testSubscribesToOrientationChangesOnStart() {
821         verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong());
822     }
823 
824     @Test
testOnBiometricPromptShownCallback()825     public void testOnBiometricPromptShownCallback() {
826         // GIVEN a callback is registered
827         AuthController.Callback callback = mock(AuthController.Callback.class);
828         mAuthController.addCallback(callback);
829 
830         // WHEN dialog is shown
831         showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
832 
833         // THEN callback should be received
834         verify(callback).onBiometricPromptShown();
835     }
836 
837     @Test
testOnBiometricPromptDismissedCallback()838     public void testOnBiometricPromptDismissedCallback() {
839         // GIVEN a callback is registered
840         AuthController.Callback callback = mock(AuthController.Callback.class);
841         mAuthController.addCallback(callback);
842 
843         // WHEN dialog is shown and then dismissed
844         showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
845         mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
846                 null /* credentialAttestation */,
847                 mAuthController.mCurrentDialog.getRequestId());
848 
849         // THEN callback should be received
850         verify(callback).onBiometricPromptDismissed();
851     }
852 
853     @Test
testSubscribesToLogContext()854     public void testSubscribesToLogContext() {
855         mAuthController.setBiometricContextListener(mContextListener);
856         verify(mLogContextInteractor).addBiometricContextListener(same(mContextListener));
857     }
858 
859     @Test
testGetFingerprintSensorLocationChanges_differentRotations()860     public void testGetFingerprintSensorLocationChanges_differentRotations() {
861         // GIVEN fp default location and mocked device dimensions
862         // Rotation 0, where "o" is the location of the FP sensor, if x or y = 0, it's the edge of
863         // the screen which is why a 1x1 width and height is represented by a 2x2 grid below:
864         //   [* o]
865         //   [* *]
866         Point fpDefaultLocation = new Point(1, 0);
867         final DisplayInfo displayInfo = new DisplayInfo();
868         displayInfo.logicalWidth = 1;
869         displayInfo.logicalHeight = 1;
870 
871         // WHEN the rotation is 0, THEN no rotation applied
872         displayInfo.rotation = Surface.ROTATION_0;
873         assertEquals(
874                 fpDefaultLocation,
875                 mAuthController.rotateToCurrentOrientation(
876                         new Point(fpDefaultLocation), displayInfo)
877         );
878 
879         // WHEN the rotation is 270, THEN rotation is applied
880         //   [* *]
881         //   [* o]
882         displayInfo.rotation = Surface.ROTATION_270;
883         assertEquals(
884                 new Point(1, 1),
885                 mAuthController.rotateToCurrentOrientation(
886                         new Point(fpDefaultLocation), displayInfo)
887         );
888 
889         // WHEN the rotation is 180, THEN rotation is applied
890         //   [* *]
891         //   [o *]
892         displayInfo.rotation = Surface.ROTATION_180;
893         assertEquals(
894                 new Point(0, 1),
895                 mAuthController.rotateToCurrentOrientation(
896                         new Point(fpDefaultLocation), displayInfo)
897         );
898 
899         // WHEN the rotation is 90, THEN rotation is applied
900         //   [o *]
901         //   [* *]
902         displayInfo.rotation = Surface.ROTATION_90;
903         assertEquals(
904                 new Point(0, 0),
905                 mAuthController.rotateToCurrentOrientation(
906                         new Point(fpDefaultLocation), displayInfo)
907         );
908     }
909 
910     @Test
testGetFingerprintSensorLocationChanges_rotateRectangle()911     public void testGetFingerprintSensorLocationChanges_rotateRectangle() {
912         // GIVEN fp default location and mocked device dimensions
913         // Rotation 0, where "o" is the location of the FP sensor, if x or y = 0, it's the edge of
914         // the screen.
915         //   [* * o *]
916         //   [* * * *]
917         Point fpDefaultLocation = new Point(2, 0);
918         final DisplayInfo displayInfo = new DisplayInfo();
919         displayInfo.logicalWidth = 3;
920         displayInfo.logicalHeight = 1;
921 
922         // WHEN the rotation is 0, THEN no rotation applied
923         displayInfo.rotation = Surface.ROTATION_0;
924         assertEquals(
925                 fpDefaultLocation,
926                 mAuthController.rotateToCurrentOrientation(
927                         new Point(fpDefaultLocation), displayInfo)
928         );
929 
930         // WHEN the rotation is 180, THEN rotation is applied
931         //   [* * * *]
932         //   [* o * *]
933         displayInfo.rotation = Surface.ROTATION_180;
934         assertEquals(
935                 new Point(1, 1),
936                 mAuthController.rotateToCurrentOrientation(
937                         new Point(fpDefaultLocation), displayInfo)
938         );
939 
940         // Rotation 270 & 90 have swapped logical width and heights
941         displayInfo.logicalWidth = 1;
942         displayInfo.logicalHeight = 3;
943 
944         // WHEN the rotation is 270, THEN rotation is applied
945         //   [* *]
946         //   [* *]
947         //   [* o]
948         //   [* *]
949         displayInfo.rotation = Surface.ROTATION_270;
950         assertEquals(
951                 new Point(1, 2),
952                 mAuthController.rotateToCurrentOrientation(
953                         new Point(fpDefaultLocation), displayInfo)
954         );
955 
956         // WHEN the rotation is 90, THEN rotation is applied
957         //   [* *]
958         //   [o *]
959         //   [* *]
960         //   [* *]
961         displayInfo.rotation = Surface.ROTATION_90;
962         assertEquals(
963                 new Point(0, 1),
964                 mAuthController.rotateToCurrentOrientation(
965                         new Point(fpDefaultLocation), displayInfo)
966         );
967     }
968 
969     @Test
testUpdateFingerprintLocation_defaultPointChanges_whenConfigChanges()970     public void testUpdateFingerprintLocation_defaultPointChanges_whenConfigChanges() {
971         when(mContextSpy.getResources()).thenReturn(mResources);
972 
973         doReturn(500).when(mResources)
974                 .getDimensionPixelSize(eq(com.android.systemui.R.dimen
975                         .physical_fingerprint_sensor_center_screen_location_y));
976         mAuthController.onConfigurationChanged(null /* newConfig */);
977 
978         final Point firstFpLocation = mAuthController.getFingerprintSensorLocation();
979 
980         doReturn(1000).when(mResources)
981                 .getDimensionPixelSize(eq(com.android.systemui.R.dimen
982                         .physical_fingerprint_sensor_center_screen_location_y));
983         mAuthController.onConfigurationChanged(null /* newConfig */);
984 
985         assertNotSame(firstFpLocation, mAuthController.getFingerprintSensorLocation());
986     }
987 
988     @Test
testCloseDialog_whenGlobalActionsMenuShown()989     public void testCloseDialog_whenGlobalActionsMenuShown() throws Exception {
990         showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
991         mAuthController.handleShowGlobalActionsMenu();
992         verify(mReceiver).onDialogDismissed(
993                 eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
994                 eq(null) /* credentialAttestation */);
995     }
996 
997     @Test
testShowDialog_whenOwnerNotInForeground()998     public void testShowDialog_whenOwnerNotInForeground() {
999         PromptInfo promptInfo = createTestPromptInfo();
1000         promptInfo.setAllowBackgroundAuthentication(false);
1001         switchTask("other_package");
1002         mAuthController.showAuthenticationDialog(promptInfo,
1003                 mReceiver /* receiver */,
1004                 new int[]{1} /* sensorIds */,
1005                 false /* credentialAllowed */,
1006                 true /* requireConfirmation */,
1007                 0 /* userId */,
1008                 0 /* operationId */,
1009                 "testPackage",
1010                 REQUEST_ID);
1011 
1012         assertNull(mAuthController.mCurrentDialog);
1013         verify(mDialog1, never()).show(any(), any());
1014     }
1015 
showDialog(int[] sensorIds, boolean credentialAllowed)1016     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
1017         mAuthController.showAuthenticationDialog(createTestPromptInfo(),
1018                 mReceiver /* receiver */,
1019                 sensorIds,
1020                 credentialAllowed,
1021                 true /* requireConfirmation */,
1022                 0 /* userId */,
1023                 0 /* operationId */,
1024                 "testPackage",
1025                 REQUEST_ID);
1026     }
1027 
switchTask(String packageName)1028     private void switchTask(String packageName) {
1029         final List<ActivityManager.RunningTaskInfo> tasks = new ArrayList<>();
1030         final ActivityManager.RunningTaskInfo taskInfo =
1031                 mock(ActivityManager.RunningTaskInfo.class);
1032         taskInfo.topActivity = mock(ComponentName.class);
1033         when(taskInfo.topActivity.getPackageName()).thenReturn(packageName);
1034         tasks.add(taskInfo);
1035         when(mActivityTaskManager.getTasks(anyInt())).thenReturn(tasks);
1036     }
1037 
createTestPromptInfo()1038     private PromptInfo createTestPromptInfo() {
1039         PromptInfo promptInfo = new PromptInfo();
1040 
1041         promptInfo.setTitle("Title");
1042         promptInfo.setSubtitle("Subtitle");
1043         promptInfo.setDescription("Description");
1044         promptInfo.setNegativeButtonText("Negative Button");
1045 
1046         // RequireConfirmation is a hint to BiometricService. This can be forced to be required
1047         // by user settings, and should be tested in BiometricService.
1048         promptInfo.setConfirmationRequested(true);
1049 
1050         return promptInfo;
1051     }
1052 
generateRandomHAT()1053     private byte[] generateRandomHAT() {
1054         byte[] HAT = new byte[69];
1055         Random random = new Random();
1056         random.nextBytes(HAT);
1057         return HAT;
1058     }
1059 
enrollFingerprintAndFace(final int userId)1060     private void enrollFingerprintAndFace(final int userId) {
1061 
1062         // Enroll fingerprint
1063         verify(mFingerprintManager).registerBiometricStateListener(
1064                 mBiometricStateCaptor.capture());
1065         assertFalse(mAuthController.isFingerprintEnrolled(userId));
1066 
1067         mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
1068                 1 /* sensorId */, true /* hasEnrollments */);
1069         waitForIdleSync();
1070 
1071         assertTrue(mAuthController.isFingerprintEnrolled(userId));
1072 
1073         // Enroll face
1074         verify(mFaceManager).registerBiometricStateListener(
1075                 mBiometricStateCaptor.capture());
1076         assertFalse(mAuthController.isFaceAuthEnrolled(userId));
1077 
1078         mBiometricStateCaptor.getValue().onEnrollmentsChanged(userId,
1079                 2 /* sensorId */, true /* hasEnrollments */);
1080         waitForIdleSync();
1081 
1082         assertTrue(mAuthController.isFaceAuthEnrolled(userId));
1083     }
1084 
1085     private final class TestableAuthController extends AuthController {
1086         private int mBuildCount = 0;
1087         private PromptInfo mLastBiometricPromptInfo;
1088 
TestableAuthController(Context context)1089         TestableAuthController(Context context) {
1090             super(context, mFeatureFlags, null /* applicationCoroutineScope */,
1091                     mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
1092                     mFingerprintManager, mFaceManager, () -> mUdfpsController,
1093                     () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
1094                     mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
1095                     mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
1096                     () -> mPromptSelectionInteractor, () -> mCredentialViewModel,
1097                     () -> mPromptViewModel, mInteractionJankMonitor, mHandler, mBackgroundExecutor,
1098                     mUdfpsUtils, mVibratorHelper);
1099         }
1100 
1101         @Override
buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, WakefulnessLifecycle wakefulnessLifecycle, AuthDialogPanelInteractionDetector panelInteractionDetector, UserManager userManager, LockPatternUtils lockPatternUtils, PromptViewModel viewModel)1102         protected AuthDialog buildDialog(DelayableExecutor bgExecutor, PromptInfo promptInfo,
1103                 boolean requireConfirmation, int userId, int[] sensorIds,
1104                 String opPackageName, boolean skipIntro, long operationId, long requestId,
1105                 WakefulnessLifecycle wakefulnessLifecycle,
1106                 AuthDialogPanelInteractionDetector panelInteractionDetector,
1107                 UserManager userManager,
1108                 LockPatternUtils lockPatternUtils, PromptViewModel viewModel) {
1109 
1110             mLastBiometricPromptInfo = promptInfo;
1111 
1112             AuthDialog dialog;
1113             if (mBuildCount == 0) {
1114                 dialog = mDialog1;
1115             } else if (mBuildCount == 1) {
1116                 dialog = mDialog2;
1117             } else {
1118                 dialog = null;
1119             }
1120             mBuildCount++;
1121             return dialog;
1122         }
1123     }
1124 
1125     @Override
waitForIdleSync()1126     protected void waitForIdleSync() {
1127         mTestableLooper.processAllMessages();
1128     }
1129 }
1130