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