• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 android.server.biometrics;
18 
19 import static android.hardware.biometrics.BiometricManager.Authenticators.DEVICE_CREDENTIAL;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotEquals;
26 import static org.junit.Assert.assertThrows;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assume.assumeFalse;
29 import static org.junit.Assume.assumeTrue;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyInt;
32 import static org.mockito.ArgumentMatchers.anyObject;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.verifyNoMoreInteractions;
37 
38 import android.content.pm.PackageManager;
39 import android.hardware.biometrics.BiometricManager;
40 import android.hardware.biometrics.BiometricManager.Authenticators;
41 import android.hardware.biometrics.BiometricPrompt;
42 import android.hardware.biometrics.BiometricTestSession;
43 import android.hardware.biometrics.Flags;
44 import android.hardware.biometrics.SensorProperties;
45 import android.os.CancellationSignal;
46 import android.os.SystemClock;
47 import android.platform.test.annotations.Presubmit;
48 import android.platform.test.annotations.RequiresFlagsEnabled;
49 import android.server.biometrics.util.BiometricServiceState;
50 import android.server.biometrics.util.Utils;
51 import android.util.Log;
52 
53 import androidx.test.uiautomator.UiObject2;
54 
55 import com.android.compatibility.common.util.ApiTest;
56 import com.android.compatibility.common.util.CddTest;
57 import com.android.server.biometrics.nano.SensorStateProto;
58 
59 import org.junit.Ignore;
60 import org.junit.Test;
61 
62 import java.io.File;
63 import java.util.Random;
64 import java.util.concurrent.CountDownLatch;
65 import java.util.concurrent.TimeUnit;
66 
67 /**
68  * Simple tests.
69  */
70 @Presubmit
71 public class BiometricSimpleTests extends BiometricTestBase {
72     private static final String TAG = "BiometricTests/Simple";
73 
74     /**
75      * Tests that enrollments created via {@link BiometricTestSession} show up in the
76      * biometric dumpsys.
77      */
78     @ApiTest(apis = {
79             "android.hardware.biometrics."
80                     + "BiometricTestSession#startEnroll",
81             "android.hardware.biometrics."
82                     + "BiometricTestSession#finishEnroll"})
83     @Test
testEnroll()84     public void testEnroll() throws Exception {
85         assumeTrue(Utils.isFirstApiLevel29orGreater());
86         for (SensorProperties prop : mSensorProperties) {
87             try (BiometricTestSession session =
88                          mBiometricManager.createTestSession(prop.getSensorId())) {
89                 enrollForSensor(session, prop.getSensorId());
90             }
91         }
92     }
93 
94     /**
95      * Tests that the sensorIds retrieved via {@link BiometricManager#getSensorProperties()} and
96      * the dumpsys are consistent with each other.
97      */
98     @ApiTest(apis = {
99             "android.hardware.biometrics."
100                     + "BiometricManager#getSensorProperties"})
101     @Test
testSensorPropertiesAndDumpsysMatch()102     public void testSensorPropertiesAndDumpsysMatch() throws Exception {
103         assumeTrue(Utils.isFirstApiLevel29orGreater());
104         final BiometricServiceState state = getCurrentState();
105 
106         assertEquals(mSensorProperties.size(), state.mSensorStates.sensorStates.size());
107         for (SensorProperties prop : mSensorProperties) {
108             assertTrue(state.mSensorStates.sensorStates.containsKey(prop.getSensorId()));
109         }
110     }
111 
112     /**
113      * Tests that the PackageManager features and biometric dumpsys are consistent with each other.
114      */
115     @ApiTest(apis = {
116             "android.content.pm."
117                     + "PackageManager#FEATURE_FINGERPRINT",
118             "android.content.pm."
119                     + "PackageManager#FEATURE_FACE"})
120     @Test
testPackageManagerAndDumpsysMatch()121     public void testPackageManagerAndDumpsysMatch() throws Exception {
122         assumeTrue(Utils.isFirstApiLevel29orGreater());
123         final BiometricServiceState state = getCurrentState();
124         final PackageManager pm = mContext.getPackageManager();
125         if (mSensorProperties.isEmpty()) {
126             assertTrue(state.mSensorStates.sensorStates.isEmpty());
127 
128             final File initGsiRc = new File("/system/system_ext/etc/init/init.gsi.rc");
129             if (!initGsiRc.exists()) {
130                 assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT));
131                 assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_FACE));
132                 assertFalse(pm.hasSystemFeature(PackageManager.FEATURE_IRIS));
133             }
134 
135             assertTrue(state.mSensorStates.sensorStates.isEmpty());
136         } else {
137             assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT),
138                     state.mSensorStates.containsModality(SensorStateProto.FINGERPRINT));
139             assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_FACE),
140                     state.mSensorStates.containsModality(SensorStateProto.FACE));
141             assertEquals(pm.hasSystemFeature(PackageManager.FEATURE_IRIS),
142                     state.mSensorStates.containsModality(SensorStateProto.IRIS));
143         }
144     }
145 
146     @ApiTest(apis = {
147             "android.hardware.biometrics."
148                     + "BiometricManager#canAuthenticate"})
149     @Test
testCanAuthenticate_whenNoSensors()150     public void testCanAuthenticate_whenNoSensors() {
151         assumeTrue(Utils.isFirstApiLevel29orGreater());
152         if (mSensorProperties.isEmpty()) {
153             assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
154                     mBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK));
155             assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
156                     mBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG));
157         }
158     }
159 
160     @Ignore("b/356789161")
161     @ApiTest(apis = {
162             "android.hardware.biometrics."
163                     + "BiometricPrompt.Builder#setConfirmationRequired",
164             "android.hardware.biometrics."
165                     + "BiometricPrompt#authenticate",
166             "android.hardware.biometrics."
167                     + "BiometricPrompt#isConfirmationRequired"})
168     @Test
testIsConfirmationRequired()169     public void testIsConfirmationRequired() throws Exception {
170         assumeTrue(Utils.isFirstApiLevel29orGreater());
171         for (SensorProperties props : mSensorProperties) {
172             if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) {
173                 continue;
174             }
175 
176             Log.d(TAG, "testIsConfirmationRequired, sensor: " + props.getSensorId());
177 
178             try (BiometricTestSession session =
179                          mBiometricManager.createTestSession(props.getSensorId())) {
180 
181                 setUpNonConvenienceSensorEnrollment(props, session);
182 
183                 BiometricPrompt.AuthenticationCallback callback =
184                         mock(BiometricPrompt.AuthenticationCallback.class);
185                 BiometricPrompt prompt = showDefaultBiometricPrompt(props.getSensorId(), callback,
186                         new CancellationSignal());
187 
188                 assertTrue(prompt.isConfirmationRequired());
189                 successfullyAuthenticate(session, 0 /* userId */, callback);
190             }
191         }
192     }
193 
194     @ApiTest(apis = {
195             "android.hardware.biometrics."
196                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
197             "android.hardware.biometrics."
198                     + "BiometricPrompt#authenticate",
199             "android.hardware.biometrics."
200                     + "BiometricPrompt#getAllowedAuthenticators"})
201     @Test
testSetAllowedAuthenticators_weakBiometric()202     public void testSetAllowedAuthenticators_weakBiometric() {
203         testSetAllowedAuthenticators(Authenticators.BIOMETRIC_WEAK);
204     }
205 
206     @ApiTest(apis = {
207             "android.hardware.biometrics."
208                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
209             "android.hardware.biometrics."
210                     + "BiometricPrompt#authenticate",
211             "android.hardware.biometrics."
212                     + "BiometricPrompt#getAllowedAuthenticators"})
213     @Test
testSetAllowedAuthenticators_strongBiometric()214     public void testSetAllowedAuthenticators_strongBiometric() {
215         testSetAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG);
216     }
217 
218     @ApiTest(apis = {
219             "android.hardware.biometrics."
220                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
221             "android.hardware.biometrics."
222                     + "BiometricPrompt#authenticate",
223             "android.hardware.biometrics."
224                     + "BiometricPrompt#getAllowedAuthenticators"})
225     @Test
testSetAllowedAuthenticators_credential()226     public void testSetAllowedAuthenticators_credential() {
227         testSetAllowedAuthenticators(Authenticators.DEVICE_CREDENTIAL);
228     }
229 
230     @ApiTest(apis = {
231             "android.hardware.biometrics."
232                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
233             "android.hardware.biometrics."
234                     + "BiometricPrompt#authenticate",
235             "android.hardware.biometrics."
236                     + "BiometricPrompt#getAllowedAuthenticators"})
237     @Test
testSetAllowedAuthenticators_weakBiometricAndCredential()238     public void testSetAllowedAuthenticators_weakBiometricAndCredential() {
239         testSetAllowedAuthenticators(
240                 Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL);
241     }
242 
243     @ApiTest(apis = {
244             "android.hardware.biometrics."
245                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
246             "android.hardware.biometrics."
247                     + "BiometricPrompt#authenticate",
248             "android.hardware.biometrics."
249                     + "BiometricPrompt#getAllowedAuthenticators"})
250     @Test
testSetAllowedAuthenticators_StrongBiometricAndCredential()251     public void testSetAllowedAuthenticators_StrongBiometricAndCredential() {
252         testSetAllowedAuthenticators(
253                 Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL);
254     }
255 
testSetAllowedAuthenticators(int authenticators)256     private void testSetAllowedAuthenticators(int authenticators) {
257         assumeTrue(Utils.isFirstApiLevel29orGreater());
258         BiometricPrompt prompt = showBiometricPromptWithAuthenticators(authenticators);
259         assertEquals(authenticators, prompt.getAllowedAuthenticators());
260     }
261 
262     @ApiTest(apis = {
263             "android.hardware.biometrics."
264                     + "BiometricManager#canAuthenticate",
265             "android.hardware.biometrics."
266                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
267             "android.hardware.biometrics."
268                     + "BiometricPrompt#authenticate"})
269     @Test
testInvalidInputs()270     public void testInvalidInputs() {
271         assumeTrue(Utils.isFirstApiLevel29orGreater());
272         for (int i = 0; i < 32; i++) {
273             final int authenticator = 1 << i;
274             // If it's a public constant, no need to test
275             if (Utils.isPublicAuthenticatorConstant(authenticator)) {
276                 continue;
277             }
278 
279             // Test canAuthenticate(int)
280             assertThrows("Invalid authenticator in canAuthenticate must throw exception: "
281                             + authenticator,
282                     Exception.class,
283                     () -> mBiometricManager.canAuthenticate(authenticator));
284 
285             // Test BiometricPrompt
286             assertThrows("Invalid authenticator in authenticate must throw exception: "
287                             + authenticator,
288                     Exception.class,
289                     () -> showBiometricPromptWithAuthenticators(authenticator));
290         }
291     }
292 
293     /**
294      * When device credential is not enrolled, check the behavior for
295      * 1) BiometricManager#canAuthenticate(DEVICE_CREDENTIAL)
296      * 2) BiometricPrompt#setAllowedAuthenticators(DEVICE_CREDENTIAL)
297      * 3) @deprecated BiometricPrompt#setDeviceCredentialAllowed(true)
298      */
299     @ApiTest(apis = {
300             "android.hardware.biometrics."
301                     + "BiometricManager#canAuthenticate",
302             "android.hardware.biometrics."
303                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
304             "android.hardware.biometrics."
305                     + "BiometricPrompt.Builder#setDeviceCredentialAllowed",
306             "android.hardware.biometrics."
307                     + "BiometricPrompt#authenticate"})
308     @Test
testWhenCredentialNotEnrolled()309     public void testWhenCredentialNotEnrolled() throws Exception {
310         assumeTrue(Utils.isFirstApiLevel29orGreater());
311         // First case above
312         final int result = mBiometricManager.canAuthenticate(BiometricManager
313                 .Authenticators.DEVICE_CREDENTIAL);
314         assertEquals(BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED, result);
315 
316         // Second case above
317         BiometricPrompt.AuthenticationCallback callback =
318                 mock(BiometricPrompt.AuthenticationCallback.class);
319         showCredentialOnlyBiometricPrompt(callback, new CancellationSignal(),
320                 false /* shouldShow */);
321         verify(callback).onAuthenticationError(
322                 eq(BiometricPrompt.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL),
323                 any());
324 
325         // Third case above. Since the deprecated API is intended to allow credential in addition
326         // to biometrics, we should be receiving BIOMETRIC_ERROR_NO_BIOMETRICS.
327         final boolean noSensors = mSensorProperties.isEmpty();
328         int expectedError;
329         if (noSensors) {
330             expectedError = BiometricPrompt.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL;
331         } else if (hasOnlyConvenienceSensors()) {
332             expectedError = BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT;
333         } else {
334             expectedError = BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS;
335         }
336         callback = mock(BiometricPrompt.AuthenticationCallback.class);
337         showDeviceCredentialAllowedBiometricPrompt(callback, new CancellationSignal(),
338                 false /* shouldShow */);
339         verify(callback).onAuthenticationError(
340                 eq(expectedError),
341                 any());
342     }
343 
hasOnlyConvenienceSensors()344     private boolean hasOnlyConvenienceSensors() {
345         for (SensorProperties sensor : mSensorProperties) {
346             if (sensor.getSensorStrength() != SensorProperties.STRENGTH_CONVENIENCE) {
347                 return false;
348             }
349         }
350         return true;
351     }
352 
353     /**
354      * When device credential is enrolled, check the behavior for
355      * 1) BiometricManager#canAuthenticate(DEVICE_CREDENTIAL)
356      * 2a) Successfully authenticating BiometricPrompt#setAllowedAuthenticators(DEVICE_CREDENTIAL)
357      * 2b) Cancelling authentication for the above
358      * 3a) @deprecated BiometricPrompt#setDeviceCredentialALlowed(true)
359      * 3b) Cancelling authentication for the above
360      * 4) Cancelling auth for options 2) and 3)
361      */
362     @ApiTest(apis = {
363             "android.hardware.biometrics."
364                     + "BiometricManager#canAuthenticate",
365             "android.hardware.biometrics."
366                     + "BiometricPrompt.Builder#setAllowedAuthenticators",
367             "android.hardware.biometrics."
368                     + "BiometricPrompt.Builder#setDeviceCredentialAllowed",
369             "android.hardware.biometrics."
370                     + "BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded",
371             "android.hardware.biometrics."
372                     + "BiometricPrompt#authenticate"})
373     @Test
testWhenCredentialEnrolled()374     public void testWhenCredentialEnrolled() throws Exception {
375         assumeTrue(Utils.isFirstApiLevel29orGreater());
376         //TODO: b/331955301 need to update Auto biometric UI
377         assumeFalse(isCar());
378         try (CredentialSession session = new CredentialSession()) {
379             session.setCredential();
380 
381             // First case above
382             final int result = mBiometricManager.canAuthenticate(BiometricManager
383                     .Authenticators.DEVICE_CREDENTIAL);
384             assertEquals(BiometricManager.BIOMETRIC_SUCCESS, result);
385 
386             // 2a above
387             BiometricPrompt.AuthenticationCallback callback =
388                     mock(BiometricPrompt.AuthenticationCallback.class);
389             showCredentialOnlyBiometricPrompt(callback, new CancellationSignal(),
390                     true /* shouldShow */);
391             successfullyEnterCredential();
392             verify(callback).onAuthenticationSucceeded(any());
393 
394             // 2b above
395             CancellationSignal cancel = new CancellationSignal();
396             callback = mock(BiometricPrompt.AuthenticationCallback.class);
397             showCredentialOnlyBiometricPrompt(callback, cancel, true /* shouldShow */);
398             cancelAuthentication(cancel);
399             verify(callback).onAuthenticationError(eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
400                     any());
401 
402             // 3a above
403             callback = mock(BiometricPrompt.AuthenticationCallback.class);
404             showDeviceCredentialAllowedBiometricPrompt(callback, new CancellationSignal(),
405                     true /* shouldShow */);
406             successfullyEnterCredential();
407             verify(callback).onAuthenticationSucceeded(any());
408 
409             // 3b above
410             cancel = new CancellationSignal();
411             callback = mock(BiometricPrompt.AuthenticationCallback.class);
412             showDeviceCredentialAllowedBiometricPrompt(callback, cancel, true /* shouldShow */);
413             cancelAuthentication(cancel);
414             verify(callback).onAuthenticationError(eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
415                     any());
416         }
417     }
418 
419     @CddTest(requirements = {"7.3.10/C-4-2"})
420     @ApiTest(apis = {
421             "android.hardware.biometrics."
422                     + "BiometricManager#canAuthenticate",
423             "android.hardware.biometrics."
424                     + "BiometricPrompt.AuthenticationCallback#onAuthenticationError"})
425     @Test
testSimpleBiometricAuth_convenience()426     public void testSimpleBiometricAuth_convenience() throws Exception {
427         assumeTrue(Utils.isFirstApiLevel29orGreater());
428         for (SensorProperties props : mSensorProperties) {
429             if (props.getSensorStrength() != SensorProperties.STRENGTH_CONVENIENCE) {
430                 continue;
431             }
432 
433             Log.d(TAG, "testSimpleBiometricAuth_convenience, sensor: " + props.getSensorId());
434 
435             try (BiometricTestSession session =
436                          mBiometricManager.createTestSession(props.getSensorId())) {
437 
438                 // Let's just try to check+auth against WEAK, since CONVENIENCE isn't even
439                 // exposed to public BiometricPrompt APIs (as intended).
440                 final int authenticatorStrength = Authenticators.BIOMETRIC_WEAK;
441                 assertNotEquals("Sensor: " + props.getSensorId()
442                                 + ", strength: " + props.getSensorStrength(),
443                         BiometricManager.BIOMETRIC_SUCCESS,
444                         mBiometricManager.canAuthenticate(authenticatorStrength));
445 
446                 enrollForSensor(session, props.getSensorId());
447 
448                 assertNotEquals("Sensor: " + props.getSensorId()
449                                 + ", strength: " + props.getSensorStrength(),
450                         BiometricManager.BIOMETRIC_SUCCESS,
451                         mBiometricManager.canAuthenticate(authenticatorStrength));
452 
453                 BiometricPrompt.AuthenticationCallback callback =
454                         mock(BiometricPrompt.AuthenticationCallback.class);
455 
456                 showDefaultBiometricPrompt(props.getSensorId(), callback,
457                         new CancellationSignal());
458 
459                 verify(callback).onAuthenticationError(anyInt(), anyObject());
460             }
461         }
462     }
463 
464     /**
465      * Tests that the values specified through the public APIs are shown on the BiometricPrompt UI
466      * when biometric auth is requested.
467      *
468      * Upon successful authentication, checks that the result is
469      * {@link BiometricPrompt#AUTHENTICATION_RESULT_TYPE_BIOMETRIC}
470      *
471      * TODO(b/236763921): fix this test and unignore.
472      */
473     @Ignore
474     @CddTest(requirements = {"7.3.10/C-4-2"})
475     @ApiTest(apis = {
476             "android.hardware.biometrics."
477                     + "BiometricManager#canAuthenticate",
478             "android.hardware.biometrics."
479                     + "BiometricPrompt.Builder#setTitle",
480             "android.hardware.biometrics."
481                     + "BiometricPrompt.Builder#setSubtitle",
482             "android.hardware.biometrics."
483                     + "BiometricPrompt.Builder#setDescription",
484             "android.hardware.biometrics."
485                     + "BiometricPrompt.Builder#setNegativeButton",
486             "android.hardware.biometrics."
487                     + "BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded",
488             "android.hardware.biometrics."
489                     + "BiometricPrompt#authenticate",
490             "android.hardware.biometrics."
491                     + "BiometricPrompt.AuthenticationResult#getAuthenticationType"})
492     @Test
testSimpleBiometricAuth_nonConvenience()493     public void testSimpleBiometricAuth_nonConvenience() throws Exception {
494         assumeTrue(Utils.isFirstApiLevel29orGreater());
495         for (SensorProperties props : mSensorProperties) {
496             if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) {
497                 continue;
498             }
499 
500             Log.d(TAG, "testSimpleBiometricAuth, sensor: " + props.getSensorId());
501 
502             try (BiometricTestSession session =
503                          mBiometricManager.createTestSession(props.getSensorId())) {
504 
505                 setUpNonConvenienceSensorEnrollment(props, session);
506 
507                 final Random random = new Random();
508                 final String randomTitle = String.valueOf(random.nextInt(10000));
509                 final String randomSubtitle = String.valueOf(random.nextInt(10000));
510                 final String randomDescription = String.valueOf(random.nextInt(10000));
511                 final String randomNegativeButtonText = String.valueOf(random.nextInt(10000));
512 
513                 BiometricPrompt.AuthenticationCallback callback =
514                         mock(BiometricPrompt.AuthenticationCallback.class);
515                 showDefaultBiometricPromptWithContents(props.getSensorId(), 0 /* userId */,
516                         true /* requireConfirmation */, callback, randomTitle, randomSubtitle,
517                         randomDescription, null /* contentView */, randomNegativeButtonText);
518 
519                 final UiObject2 actualTitle = findView(TITLE_VIEW);
520                 final UiObject2 actualSubtitle = findView(SUBTITLE_VIEW);
521                 final UiObject2 actualDescription = findView(DESCRIPTION_VIEW);
522                 final UiObject2 actualNegativeButton = findView(BUTTON_ID_NEGATIVE);
523                 assertEquals(randomTitle, actualTitle.getText());
524                 assertEquals(randomSubtitle, actualSubtitle.getText());
525                 assertEquals(randomDescription, actualDescription.getText());
526                 assertEquals(randomNegativeButtonText, actualNegativeButton.getText());
527 
528                 // Finish auth
529                 successfullyAuthenticate(session, 0 /* userId */, callback);
530             }
531         }
532     }
533 
534     /**
535      * Tests that the values specified through the public APIs are shown on the BiometricPrompt UI
536      * when credential auth is requested.
537      *
538      * Upon successful authentication, checks that the result is
539      * {@link BiometricPrompt#AUTHENTICATION_RESULT_TYPE_BIOMETRIC}
540      */
541     @ApiTest(apis = {
542             "android.hardware.biometrics."
543                     + "BiometricPrompt.Builder#setTitle",
544             "android.hardware.biometrics."
545                     + "BiometricPrompt.Builder#setSubtitle",
546             "android.hardware.biometrics."
547                     + "BiometricPrompt.Builder#setDescription",
548             "android.hardware.biometrics."
549                     + "BiometricPrompt#authenticate",
550             "android.hardware.biometrics."
551                     + "BiometricPrompt.AuthenticationResult#getAuthenticationType"})
552     @Test
testSimpleCredentialAuth()553     public void testSimpleCredentialAuth() throws Exception {
554         assumeTrue(Utils.isFirstApiLevel29orGreater());
555         //TODO: b/331955301 need to update Auto biometric UI
556         assumeFalse(isCar());
557         try (CredentialSession session = new CredentialSession()) {
558             session.setCredential();
559 
560             final Random random = new Random();
561             final String randomTitle = String.valueOf(random.nextInt(10000));
562             final String randomSubtitle = String.valueOf(random.nextInt(10000));
563             final String randomDescription = String.valueOf(random.nextInt(10000));
564 
565             CountDownLatch latch = new CountDownLatch(1);
566             BiometricPrompt.AuthenticationCallback callback =
567                     new BiometricPrompt.AuthenticationCallback() {
568                         @Override
569                         public void onAuthenticationSucceeded(
570                                 BiometricPrompt.AuthenticationResult result) {
571                             assertEquals("Must be TYPE_CREDENTIAL",
572                                     BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL,
573                                     result.getAuthenticationType());
574                             latch.countDown();
575                         }
576                     };
577             showCredentialOnlyBiometricPromptWithContents(callback, new CancellationSignal(),
578                     true /* shouldShow */, randomTitle, randomSubtitle, randomDescription,
579                     null /* contentView */);
580 
581             final UiObject2 actualTitle = findView(TITLE_VIEW);
582             final UiObject2 actualSubtitle = findView(SUBTITLE_VIEW);
583             final UiObject2 actualDescription = findView(DESCRIPTION_VIEW);
584             assertEquals(randomTitle, actualTitle.getText());
585             assertEquals(randomSubtitle, actualSubtitle.getText());
586             assertEquals(randomDescription, actualDescription.getText());
587 
588             // Finish auth
589             successfullyEnterCredential();
590             latch.await(3, TimeUnit.SECONDS);
591         }
592     }
593 
594     /**
595      * Tests that cancelling auth succeeds, and that ERROR_CANCELED is received.
596      */
597     @ApiTest(apis = {
598             "android.hardware.biometrics."
599                     + "BiometricPrompt.AuthenticationCallback#onAuthenticationError"})
600     @Test
testBiometricCancellation()601     public void testBiometricCancellation() throws Exception {
602         assumeTrue(Utils.isFirstApiLevel29orGreater());
603         for (SensorProperties props : mSensorProperties) {
604             if (props.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE) {
605                 continue;
606             }
607 
608             try (BiometricTestSession session =
609                          mBiometricManager.createTestSession(props.getSensorId())) {
610                 enrollForSensor(session, props.getSensorId());
611 
612                 BiometricPrompt.AuthenticationCallback callback =
613                         mock(BiometricPrompt.AuthenticationCallback.class);
614                 CancellationSignal cancellationSignal = new CancellationSignal();
615 
616                 showDefaultBiometricPrompt(props.getSensorId(), callback, cancellationSignal);
617 
618                 cancelAuthentication(cancellationSignal);
619                 verify(callback).onAuthenticationError(eq(BiometricPrompt.BIOMETRIC_ERROR_CANCELED),
620                         any());
621                 verifyNoMoreInteractions(callback);
622             }
623         }
624     }
625 
626     /**
627      * Tests that {@link BiometricManager#getLastAuthenticationTime(int)} result changes
628      * appropriately for DEVICE_CREDENTIAL after a PIN unlock.
629      */
630     @Test
631     @RequiresFlagsEnabled(Flags.FLAG_LAST_AUTHENTICATION_TIME)
testGetLastAuthenticationTime_unlockWithCorrectDeviceCredential()632     public void testGetLastAuthenticationTime_unlockWithCorrectDeviceCredential() throws Exception {
633         try (CredentialSession credentialSession = new CredentialSession()) {
634             credentialSession.setCredential();
635 
636             final long startTime = SystemClock.elapsedRealtime();
637 
638             credentialSession.verifyCredential();
639 
640             // There's a race between the auth token being sent to keystore2 and the
641             // getLastAuthenticationTime() call, so retry if we don't get a valid time.
642             long lastAuthTime = BiometricManager.BIOMETRIC_NO_AUTHENTICATION;
643             for (int i = 0; i < 10; i++) {
644                 lastAuthTime = mBiometricManager.getLastAuthenticationTime(
645                         DEVICE_CREDENTIAL);
646                 if (lastAuthTime != BiometricManager.BIOMETRIC_NO_AUTHENTICATION) {
647                     break;
648                 }
649 
650                 Thread.sleep(100);
651             }
652 
653             assertThat(lastAuthTime).isGreaterThan(startTime);
654         }
655     }
656 
657     /**
658      * Tests that {@link BiometricManager#getLastAuthenticationTime(int)} result does not change
659      * when an incorrect PIN is entered.
660      */
661     @Test
662     @RequiresFlagsEnabled(Flags.FLAG_LAST_AUTHENTICATION_TIME)
testGetLastAuthenticationTime_unlockWithIncorrectDeviceCredential()663     public void testGetLastAuthenticationTime_unlockWithIncorrectDeviceCredential()
664             throws Exception {
665         try (CredentialSession credentialSession = new CredentialSession()) {
666             credentialSession.setCredential();
667 
668             final long initialLastAuthTime = mBiometricManager.getLastAuthenticationTime(
669                     DEVICE_CREDENTIAL);
670 
671             credentialSession.verifyIncorrectCredential();
672 
673             long lastAuthTime = mBiometricManager.getLastAuthenticationTime(
674                     DEVICE_CREDENTIAL);
675 
676             assertThat(lastAuthTime).isEqualTo(initialLastAuthTime);
677         }
678     }
679 
680     /**
681      * Tests that {@link BiometricManager#getLastAuthenticationTime(int)} result returns
682      * {@link BiometricManager#BIOMETRIC_NO_AUTHENTICATION} if there is no password/PIN set.
683      */
684     @Test
685     @RequiresFlagsEnabled(Flags.FLAG_LAST_AUTHENTICATION_TIME)
testGetLastAuthenticationTime_noCredential()686     public void testGetLastAuthenticationTime_noCredential() throws Exception {
687         final long lastAuthTime = mBiometricManager.getLastAuthenticationTime(
688                 DEVICE_CREDENTIAL);
689 
690         assertThat(lastAuthTime).isEqualTo(BiometricManager.BIOMETRIC_NO_AUTHENTICATION);
691     }
692 }
693