1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.server.biometrics; 18 19 import static android.os.PowerManager.FULL_WAKE_LOCK; 20 import static android.server.biometrics.util.SensorStates.SensorState; 21 import static android.server.biometrics.util.SensorStates.UserState; 22 23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 24 25 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_IDLE; 26 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_PENDING_CONFIRM; 27 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING; 28 import static com.android.server.biometrics.nano.BiometricServiceStateProto.STATE_SHOWING_DEVICE_CREDENTIAL; 29 30 import static com.google.common.truth.Truth.assertWithMessage; 31 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertNotNull; 34 import static org.junit.Assert.assertTrue; 35 import static org.junit.Assert.fail; 36 import static org.junit.Assume.assumeTrue; 37 import static org.mockito.Mockito.mock; 38 import static org.mockito.Mockito.verify; 39 40 import android.app.Instrumentation; 41 import android.content.ComponentName; 42 import android.content.pm.PackageManager; 43 import android.graphics.Bitmap; 44 import android.hardware.biometrics.BiometricManager; 45 import android.hardware.biometrics.BiometricManager.Authenticators; 46 import android.hardware.biometrics.BiometricPrompt; 47 import android.hardware.biometrics.BiometricTestSession; 48 import android.hardware.biometrics.PromptContentView; 49 import android.hardware.biometrics.PromptVerticalListContentView; 50 import android.hardware.biometrics.SensorProperties; 51 import android.os.Bundle; 52 import android.os.CancellationSignal; 53 import android.os.Handler; 54 import android.os.Looper; 55 import android.os.PowerManager; 56 import android.platform.test.flag.junit.CheckFlagsRule; 57 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 58 import android.server.biometrics.util.BiometricCallbackHelper; 59 import android.server.biometrics.util.BiometricServiceState; 60 import android.server.biometrics.util.SensorStates; 61 import android.server.biometrics.util.TestSessionList; 62 import android.server.biometrics.util.Utils; 63 import android.server.wm.ActivityManagerTestBase; 64 import android.server.wm.TestJournalProvider.TestJournal; 65 import android.server.wm.UiDeviceUtils; 66 import android.server.wm.WindowManagerState; 67 import android.util.Log; 68 69 import androidx.annotation.NonNull; 70 import androidx.annotation.Nullable; 71 import androidx.test.uiautomator.By; 72 import androidx.test.uiautomator.UiDevice; 73 import androidx.test.uiautomator.UiObject2; 74 import androidx.test.uiautomator.Until; 75 76 import com.android.server.biometrics.nano.BiometricServiceStateProto; 77 78 import org.junit.After; 79 import org.junit.Before; 80 import org.junit.Rule; 81 import org.mockito.ArgumentCaptor; 82 83 import java.util.ArrayList; 84 import java.util.Collections; 85 import java.util.List; 86 import java.util.concurrent.Executor; 87 88 /** 89 * Base class containing useful functionality. Actual tests should be done in subclasses. 90 */ 91 abstract class BiometricTestBase extends ActivityManagerTestBase implements TestSessionList.Idler { 92 93 @Rule 94 public final CheckFlagsRule mCheckFlagsRule = 95 DeviceFlagsValueProvider.createCheckFlagsRule(); 96 97 private static final String TAG = "BiometricTestBase"; 98 private static final String DUMPSYS_BIOMETRIC = Utils.DUMPSYS_BIOMETRIC; 99 private static final String FLAG_CLEAR_SCHEDULER_LOG = " --clear-scheduler-buffer"; 100 101 // Negative-side (left) buttons 102 protected static final String BUTTON_ID_NEGATIVE = "button_negative"; 103 protected static final String BUTTON_ID_USE_CREDENTIAL = "button_use_credential"; 104 105 // Positive-side (right) buttons 106 protected static final String BUTTON_ID_CONFIRM = "button_confirm"; 107 protected static final String BUTTON_ID_TRY_AGAIN = "button_try_again"; 108 109 // Biometric text contents 110 protected static final String SCROLL_PARENT_VIEW = "scrollView"; 111 protected static final String LOGO_VIEW = "logo"; 112 protected static final String LOGO_DESCRIPTION_VIEW = "logo_description"; 113 protected static final String TITLE_VIEW = "title"; 114 protected static final String SUBTITLE_VIEW = "subtitle"; 115 protected static final String DESCRIPTION_VIEW = "description"; 116 protected static final String CONTENT_VIEW = "customized_view"; 117 protected static final String CONTENT_DESCRIPTION_VIEW = "customized_view_description"; 118 119 protected static final String VIEW_ID_PASSWORD_FIELD = "lockPassword"; 120 protected static final String KEY_ENTER = "key_enter"; 121 private static final int VIEW_WAIT_TIME_MS = 10000; 122 @NonNull protected Instrumentation mInstrumentation; 123 @NonNull protected BiometricManager mBiometricManager; 124 @NonNull protected List<SensorProperties> mSensorProperties; 125 @Nullable private PowerManager.WakeLock mWakeLock; 126 @NonNull protected UiDevice mDevice; 127 protected boolean mHasStrongBox; 128 129 /** 130 * Expose this functionality to our package, since ActivityManagerTestBase's is `protected`. 131 * @param componentName 132 */ launchActivity(@onNull ComponentName componentName)133 void launchActivity(@NonNull ComponentName componentName) { 134 super.launchActivity(componentName); 135 } 136 137 @Override waitForIdleSensors()138 public void waitForIdleSensors() { 139 try { 140 Utils.waitForIdleService(this::getSensorStates); 141 } catch (Exception e) { 142 Log.e(TAG, "Exception when waiting for idle", e); 143 } 144 } 145 146 /** @see Utils#getBiometricServiceCurrentState() */ 147 @NonNull getCurrentState()148 protected BiometricServiceState getCurrentState() throws Exception { 149 return Utils.getBiometricServiceCurrentState(); 150 } 151 152 @NonNull getCurrentStateAndClearSchedulerLog()153 protected BiometricServiceState getCurrentStateAndClearSchedulerLog() throws Exception { 154 final byte[] dump = Utils.executeShellCommand(DUMPSYS_BIOMETRIC 155 + FLAG_CLEAR_SCHEDULER_LOG); 156 final BiometricServiceStateProto proto = BiometricServiceStateProto.parseFrom(dump); 157 return BiometricServiceState.parseFrom(proto); 158 } 159 160 @Nullable findView(String id)161 protected UiObject2 findView(String id) { 162 Log.d(TAG, "Finding view: " + id); 163 return mDevice.findObject(By.res(mBiometricManager.getUiPackage(), id)); 164 } 165 166 @Nullable findViewByText(String text)167 protected UiObject2 findViewByText(String text) { 168 Log.d(TAG, "Finding view by text: " + text); 169 return mDevice.findObject(By.text(text)); 170 } 171 172 @Nullable waitForView(String id)173 protected UiObject2 waitForView(String id) { 174 Log.d(TAG, "Waiting for view " + id); 175 return mDevice.wait(Until.findObject(By.res(mBiometricManager.getUiPackage(), id)), 176 VIEW_WAIT_TIME_MS); 177 } 178 waitAndPressButton(String id)179 protected void waitAndPressButton(String id) { 180 final UiObject2 button = waitForView(id); 181 assertNotNull(button); 182 Log.d(TAG, "Waiting & clicking button: " + id); 183 button.click(); 184 } findAndPressButton(String id)185 protected void findAndPressButton(String id) { 186 final UiObject2 button = findView(id); 187 assertNotNull(button); 188 Log.d(TAG, "Clicking button: " + id); 189 button.click(); 190 } 191 getSensorStates()192 protected SensorStates getSensorStates() throws Exception { 193 return getCurrentState().mSensorStates; 194 } 195 waitForState(@iometricServiceState.AuthSessionState int state)196 protected void waitForState(@BiometricServiceState.AuthSessionState int state) 197 throws Exception { 198 for (int i = 0; i < 20; i++) { 199 final BiometricServiceState serviceState = getCurrentState(); 200 if (serviceState.mState != state) { 201 Log.d(TAG, "Not in state " + state + " yet, current: " + serviceState.mState); 202 Thread.sleep(300); 203 } else { 204 return; 205 } 206 } 207 Log.d(TAG, "Timed out waiting for state to become: " + state); 208 } 209 waitForStateNotEqual(@iometricServiceState.AuthSessionState int state)210 private void waitForStateNotEqual(@BiometricServiceState.AuthSessionState int state) 211 throws Exception { 212 for (int i = 0; i < 20; i++) { 213 final BiometricServiceState serviceState = getCurrentState(); 214 if (serviceState.mState == state) { 215 Log.d(TAG, "Not out of state yet, current: " + serviceState.mState); 216 Thread.sleep(300); 217 } else { 218 return; 219 } 220 } 221 Log.d(TAG, "Timed out waiting for state to not equal: " + state); 222 } 223 anyEnrollmentsExist()224 private boolean anyEnrollmentsExist() throws Exception { 225 final BiometricServiceState serviceState = getCurrentState(); 226 227 for (SensorState sensorState : serviceState.mSensorStates.sensorStates.values()) { 228 for (UserState userState : sensorState.getUserStates().values()) { 229 if (userState.numEnrolled != 0) { 230 Log.d(TAG, "Enrollments still exist: " + serviceState); 231 return true; 232 } 233 } 234 } 235 return false; 236 } 237 setUpNonConvenienceSensorEnrollment(SensorProperties props, BiometricTestSession session)238 protected void setUpNonConvenienceSensorEnrollment(SensorProperties props, 239 BiometricTestSession session) throws Exception { 240 waitForAllUnenrolled(); 241 final int authenticatorStrength = 242 Utils.testApiStrengthToAuthenticatorStrength(props.getSensorStrength()); 243 244 assertWithMessage("Sensor: " + props.getSensorId() 245 + ", strength: " + props.getSensorStrength()).that( 246 mBiometricManager.canAuthenticate(authenticatorStrength)).isEqualTo( 247 BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED); 248 249 enrollForSensor(session, props.getSensorId()); 250 251 assertWithMessage("Sensor: " + props.getSensorId() 252 + ", strength: " + props.getSensorStrength()).that( 253 mBiometricManager.canAuthenticate(authenticatorStrength)).isEqualTo( 254 BiometricManager.BIOMETRIC_SUCCESS); 255 } 256 successfullyAuthenticate(@onNull BiometricTestSession session, int userId)257 private void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId) 258 throws Exception { 259 session.acceptAuthentication(userId); 260 mInstrumentation.waitForIdleSync(); 261 waitForStateNotEqual(STATE_AUTH_STARTED_UI_SHOWING); 262 BiometricServiceState state = getCurrentState(); 263 Log.d(TAG, "State after acceptAuthentication: " + state); 264 if (state.mState == STATE_AUTH_PENDING_CONFIRM) { 265 waitAndPressButton(BUTTON_ID_CONFIRM); 266 mInstrumentation.waitForIdleSync(); 267 waitForState(STATE_AUTH_IDLE); 268 } else { 269 waitForState(STATE_AUTH_IDLE); 270 } 271 272 assertEquals("Failed to become idle after authenticating", 273 STATE_AUTH_IDLE, getCurrentState().mState); 274 } 275 successfullyAuthenticate(@onNull BiometricTestSession session, int userId, TestJournal journal)276 protected void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId, 277 TestJournal journal) throws Exception { 278 successfullyAuthenticate(session, userId); 279 mInstrumentation.waitForIdleSync(); 280 BiometricCallbackHelper.State callbackState = getCallbackState(journal); 281 assertNotNull(callbackState); 282 assertEquals(callbackState.toString(), 1, callbackState.mNumAuthAccepted); 283 assertEquals(callbackState.toString(), 0, callbackState.mAcquiredReceived.size()); 284 assertEquals(callbackState.toString(), 0, callbackState.mErrorsReceived.size()); 285 } 286 successfullyAuthenticate(@onNull BiometricTestSession session, int userId, BiometricPrompt.AuthenticationCallback callback)287 protected void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId, 288 BiometricPrompt.AuthenticationCallback callback) throws Exception { 289 successfullyAuthenticate(session, userId); 290 ArgumentCaptor<BiometricPrompt.AuthenticationResult> resultCaptor = 291 ArgumentCaptor.forClass(BiometricPrompt.AuthenticationResult.class); 292 verify(callback).onAuthenticationSucceeded(resultCaptor.capture()); 293 assertEquals("Must be TYPE_BIOMETRIC", 294 BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC, 295 resultCaptor.getValue().getAuthenticationType()); 296 } 297 successfullyEnterCredential()298 protected void successfullyEnterCredential() throws Exception { 299 waitForState(STATE_SHOWING_DEVICE_CREDENTIAL); 300 BiometricServiceState state = getCurrentState(); 301 assertTrue(state.toString(), state.mSensorStates.areAllSensorsIdle()); 302 assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState); 303 304 // Wait for any animations to complete. Ideally, this should be reflected in 305 // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different processes 306 // so we'd need to add some additional plumbing. We can improve this in the future. 307 // TODO(b/152240892) 308 Thread.sleep(1000); 309 310 // Enter credential. AuthSession done, authentication callback received 311 final UiObject2 passwordField = findView(VIEW_ID_PASSWORD_FIELD); 312 Log.d(TAG, "Focusing, entering, submitting credential"); 313 passwordField.click(); 314 passwordField.setText(LOCK_CREDENTIAL); 315 if (mInstrumentation.getContext().getPackageManager().hasSystemFeature( 316 PackageManager.FEATURE_AUTOMOTIVE)) { 317 final UiObject2 enterButton = findView(KEY_ENTER); 318 enterButton.click(); 319 } else { 320 mDevice.pressEnter(); 321 } 322 waitForState(STATE_AUTH_IDLE); 323 324 state = getCurrentState(); 325 assertEquals(state.toString(), STATE_AUTH_IDLE, state.mState); 326 } 327 cancelAuthentication(@onNull CancellationSignal cancel)328 protected void cancelAuthentication(@NonNull CancellationSignal cancel) throws Exception { 329 cancel.cancel(); 330 mInstrumentation.waitForIdleSync(); 331 waitForState(STATE_AUTH_IDLE); 332 333 //TODO(b/152240892): Currently BiometricService does not get a signal from SystemUI 334 // when the dialog finishes animating away. 335 Thread.sleep(1000); 336 337 BiometricServiceState state = getCurrentState(); 338 assertEquals("Not idle after requesting cancellation", state.mState, STATE_AUTH_IDLE); 339 } 340 waitForAllUnenrolled()341 protected void waitForAllUnenrolled() throws Exception { 342 for (int i = 0; i < 20; i++) { 343 if (anyEnrollmentsExist()) { 344 Log.d(TAG, "Enrollments still exist.."); 345 Thread.sleep(300); 346 } else { 347 return; 348 } 349 } 350 fail("Some sensors still have enrollments. State: " + getCurrentState()); 351 } 352 353 /** 354 * Shows a BiometricPrompt that specifies {@link Authenticators#DEVICE_CREDENTIAL}. 355 */ showCredentialOnlyBiometricPrompt( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow)356 protected void showCredentialOnlyBiometricPrompt( 357 @NonNull BiometricPrompt.AuthenticationCallback callback, 358 @NonNull CancellationSignal cancellationSignal, 359 boolean shouldShow) throws Exception { 360 showCredentialOnlyBiometricPromptWithContents(callback, cancellationSignal, shouldShow, 361 "Title", "Subtitle", "Description", null /* contentView */); 362 } 363 364 /** 365 * Shows a BiometricPrompt that specifies {@link Authenticators#DEVICE_CREDENTIAL} 366 * and the specified contents. 367 */ showCredentialOnlyBiometricPromptWithContents( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow, @NonNull String title, @NonNull String subtitle, @NonNull String description, @Nullable PromptContentView contentView)368 protected void showCredentialOnlyBiometricPromptWithContents( 369 @NonNull BiometricPrompt.AuthenticationCallback callback, 370 @NonNull CancellationSignal cancellationSignal, boolean shouldShow, 371 @NonNull String title, @NonNull String subtitle, 372 @NonNull String description, @Nullable PromptContentView contentView) throws Exception { 373 final Handler handler = new Handler(Looper.getMainLooper()); 374 final Executor executor = handler::post; 375 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 376 .setTitle(title) 377 .setSubtitle(subtitle) 378 .setDescription(description) 379 .setContentView(contentView) 380 .setAllowedAuthenticators(Authenticators.DEVICE_CREDENTIAL) 381 .setAllowBackgroundAuthentication(true) 382 .build(); 383 384 prompt.authenticate(cancellationSignal, executor, callback); 385 386 waitForCredentialIdle(shouldShow, contentView instanceof PromptVerticalListContentView); 387 } 388 389 /** 390 * SHows a BiometricPrompt that sets 391 * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} to true. 392 */ showDeviceCredentialAllowedBiometricPrompt( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow)393 protected void showDeviceCredentialAllowedBiometricPrompt( 394 @NonNull BiometricPrompt.AuthenticationCallback callback, 395 @NonNull CancellationSignal cancellationSignal, 396 boolean shouldShow) throws Exception { 397 final Handler handler = new Handler(Looper.getMainLooper()); 398 final Executor executor = handler::post; 399 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 400 .setTitle("Title") 401 .setSubtitle("Subtitle") 402 .setDescription("Description") 403 .setDeviceCredentialAllowed(true) 404 .setAllowBackgroundAuthentication(true) 405 .build(); 406 407 prompt.authenticate(cancellationSignal, executor, callback); 408 409 waitForCredentialIdle(shouldShow, false /* isContentViewWithMoreOptionsButton */); 410 } 411 showDefaultBiometricPrompt(int sensorId, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal)412 protected BiometricPrompt showDefaultBiometricPrompt(int sensorId, 413 @NonNull BiometricPrompt.AuthenticationCallback callback, 414 @NonNull CancellationSignal cancellationSignal) throws Exception { 415 return showDefaultBiometricPromptWithLogo(sensorId, callback, cancellationSignal, 416 -1 /* logoRes */, null /* logoBitmap */, "logo" /* logoDescription */); 417 } 418 showDefaultBiometricPromptWithLogo(int sensorId, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, int logoRes, Bitmap logoBitmap, String logoDescription)419 protected BiometricPrompt showDefaultBiometricPromptWithLogo(int sensorId, 420 @NonNull BiometricPrompt.AuthenticationCallback callback, 421 @NonNull CancellationSignal cancellationSignal, int logoRes, Bitmap logoBitmap, 422 String logoDescription) throws Exception { 423 final Handler handler = new Handler(Looper.getMainLooper()); 424 final Executor executor = handler::post; 425 final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(mContext) 426 .setTitle("Title") 427 .setSubtitle("Subtitle") 428 .setDescription("Description") 429 .setConfirmationRequired(true) 430 .setNegativeButton("Negative Button", executor, (dialog, which) -> { 431 Log.d(TAG, "Negative button pressed"); 432 }) 433 .setAllowBackgroundAuthentication(true) 434 .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId))) 435 .setLogoDescription(logoDescription); 436 437 if (logoRes != -1) { 438 builder.setLogoRes(logoRes); 439 } 440 if (logoBitmap != null) { 441 builder.setLogoBitmap(logoBitmap); 442 } 443 444 final BiometricPrompt prompt = builder.build(); 445 prompt.authenticate(cancellationSignal, executor, callback); 446 447 waitForState(STATE_AUTH_STARTED_UI_SHOWING); 448 return prompt; 449 } 450 451 /** 452 * Shows the default BiometricPrompt (sensors meeting BIOMETRIC_WEAK) with a negative button, 453 * but does not complete authentication. In other words, the dialog will stay on the screen. 454 */ showDefaultBiometricPromptWithContents(int sensorId, int userId, boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull String title, @NonNull String subtitle, @NonNull String description, @Nullable PromptContentView contentView, @NonNull String negativeButtonText)455 protected void showDefaultBiometricPromptWithContents(int sensorId, int userId, 456 boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback, 457 @NonNull String title, @NonNull String subtitle, @NonNull String description, 458 @Nullable PromptContentView contentView, @NonNull String negativeButtonText) 459 throws Exception { 460 final Handler handler = new Handler(Looper.getMainLooper()); 461 final Executor executor = handler::post; 462 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 463 .setTitle(title) 464 .setSubtitle(subtitle) 465 .setDescription(description) 466 .setContentView(contentView) 467 .setConfirmationRequired(requireConfirmation) 468 .setNegativeButton(negativeButtonText, executor, (dialog, which) -> { 469 Log.d(TAG, "Negative button pressed"); 470 }) 471 .setAllowBackgroundAuthentication(true) 472 .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId))) 473 .build(); 474 prompt.authenticate(new CancellationSignal(), executor, callback); 475 476 waitForState(STATE_AUTH_STARTED_UI_SHOWING); 477 } 478 479 /** 480 * Shows the default BiometricPrompt (sensors meeting BIOMETRIC_WEAK) with a negative button, 481 * and fakes successful authentication via TestApis. 482 */ showDefaultBiometricPromptAndAuth(@onNull BiometricTestSession session, int sensorId, int userId)483 protected void showDefaultBiometricPromptAndAuth(@NonNull BiometricTestSession session, 484 int sensorId, int userId) throws Exception { 485 BiometricPrompt.AuthenticationCallback callback = mock( 486 BiometricPrompt.AuthenticationCallback.class); 487 showDefaultBiometricPromptWithContents(sensorId, userId, false /* requireConfirmation */, 488 callback, "Title", "Subtitle", "Description", 489 null, "Negative Button"); 490 successfullyAuthenticate(session, userId); 491 } 492 showBiometricPromptWithAuthenticators(int authenticators)493 protected BiometricPrompt showBiometricPromptWithAuthenticators(int authenticators) { 494 final Handler handler = new Handler(Looper.getMainLooper()); 495 final Executor executor = handler::post; 496 final BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(mContext) 497 .setTitle("Title") 498 .setSubtitle("Subtitle") 499 .setDescription("Description") 500 .setAllowBackgroundAuthentication(true) 501 .setAllowedAuthenticators(authenticators); 502 if ((authenticators & Authenticators.DEVICE_CREDENTIAL) == 0) { 503 promptBuilder.setNegativeButton("Negative Button", executor, (dialog, which) -> { 504 Log.d(TAG, "Negative button pressed"); 505 }); 506 } 507 508 final BiometricPrompt prompt = promptBuilder.build(); 509 prompt.authenticate(new CancellationSignal(), executor, 510 new BiometricPrompt.AuthenticationCallback() { 511 @Override 512 public void onAuthenticationError(int errorCode, CharSequence errString) { 513 Log.d(TAG, "onAuthenticationError: " + errorCode); 514 } 515 516 @Override 517 public void onAuthenticationSucceeded( 518 BiometricPrompt.AuthenticationResult result) { 519 Log.d(TAG, "onAuthenticationSucceeded"); 520 } 521 }); 522 return prompt; 523 } 524 launchActivityAndWaitForResumed(@onNull ActivitySession activitySession)525 protected void launchActivityAndWaitForResumed(@NonNull ActivitySession activitySession) { 526 activitySession.start(); 527 mWmState.waitForActivityState(activitySession.getComponentName(), 528 WindowManagerState.STATE_RESUMED); 529 mInstrumentation.waitForIdleSync(); 530 } 531 closeActivity(@onNull ActivitySession activitySession)532 protected void closeActivity(@NonNull ActivitySession activitySession) throws Exception { 533 activitySession.close(); 534 mInstrumentation.waitForIdleSync(); 535 } 536 getCurrentStrength(int sensorId)537 protected int getCurrentStrength(int sensorId) throws Exception { 538 final BiometricServiceState serviceState = getCurrentState(); 539 return serviceState.mSensorStates.sensorStates.get(sensorId).getCurrentStrength(); 540 } 541 getSensorsOfTargetStrength(int targetStrength)542 protected List<Integer> getSensorsOfTargetStrength(int targetStrength) { 543 final List<Integer> sensors = new ArrayList<>(); 544 for (SensorProperties prop : mSensorProperties) { 545 if (prop.getSensorStrength() == targetStrength) { 546 sensors.add(prop.getSensorId()); 547 } 548 } 549 Log.d(TAG, "getSensorsOfTargetStrength: num of target sensors=" + sensors.size()); 550 return sensors; 551 } 552 553 @NonNull getCallbackState(@onNull TestJournal journal)554 protected static BiometricCallbackHelper.State getCallbackState(@NonNull TestJournal journal) { 555 Utils.waitFor("Waiting for authentication callback", 556 () -> journal.extras.containsKey(BiometricCallbackHelper.KEY), 557 (lastResult) -> fail("authentication callback never received - died waiting")); 558 559 final Bundle bundle = journal.extras.getBundle(BiometricCallbackHelper.KEY); 560 final BiometricCallbackHelper.State state = 561 BiometricCallbackHelper.State.fromBundle(bundle); 562 563 // Clear the extras since we want to wait for the journal to sync any new info the next 564 // time it's read 565 journal.extras.clear(); 566 567 return state; 568 } 569 570 @Before setUp()571 public void setUp() throws Exception { 572 mInstrumentation = getInstrumentation(); 573 mBiometricManager = mInstrumentation.getContext().getSystemService(BiometricManager.class); 574 575 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(); 576 mDevice = UiDevice.getInstance(mInstrumentation); 577 mSensorProperties = mBiometricManager.getSensorProperties(); 578 579 assumeTrue(mInstrumentation.getContext().getPackageManager().hasSystemFeature( 580 PackageManager.FEATURE_SECURE_LOCK_SCREEN)); 581 582 mHasStrongBox = mContext.getPackageManager().hasSystemFeature( 583 PackageManager.FEATURE_STRONGBOX_KEYSTORE); 584 585 // Keep the screen on for the duration of each test, since BiometricPrompt goes away 586 // when screen turns off. 587 final PowerManager pm = mInstrumentation.getContext().getSystemService(PowerManager.class); 588 mWakeLock = pm.newWakeLock(FULL_WAKE_LOCK, TAG); 589 mWakeLock.acquire(); 590 591 // Turn screen on and dismiss keyguard 592 UiDeviceUtils.pressWakeupButton(); 593 UiDeviceUtils.pressUnlockButton(); 594 } 595 596 @After cleanup()597 public void cleanup() { 598 mInstrumentation.waitForIdleSync(); 599 600 // Authentication lifecycle is done 601 waitForIdleSensors(); 602 603 if (mWakeLock != null) { 604 mWakeLock.release(); 605 } 606 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 607 } 608 enrollForSensor(@onNull BiometricTestSession session, int sensorId)609 protected void enrollForSensor(@NonNull BiometricTestSession session, int sensorId) 610 throws Exception { 611 Log.d(TAG, "Enrolling for sensor: " + sensorId); 612 final int userId = Utils.getUserId(); 613 614 session.startEnroll(userId); 615 mInstrumentation.waitForIdleSync(); 616 Utils.waitForBusySensor(sensorId, this::getSensorStates); 617 618 //Wait for enrollment operation in biometrics sensor to be complete before 619 //retrieving enrollment results. The operation takes a little time especically 620 //on Cutterfish where multiple biometric operations must be completed during 621 //the enrollent 622 //TODO(b/217275524) 623 Thread.sleep(200); 624 session.finishEnroll(userId); 625 mInstrumentation.waitForIdleSync(); 626 Utils.waitForIdleService(this::getSensorStates); 627 628 final BiometricServiceState state = getCurrentState(); 629 assertEquals("Sensor: " + sensorId + " should have exactly one enrollment", 630 1, state.mSensorStates.sensorStates 631 .get(sensorId).getUserStates().get(userId).numEnrolled); 632 } 633 waitForCredentialIdle(boolean shouldShow, boolean isVerticalContentView)634 protected void waitForCredentialIdle(boolean shouldShow, 635 boolean isVerticalContentView) 636 throws Exception { 637 boolean shouldShowBpWithoutIconForCredential = Utils.shouldShowBpWithoutIconForCredential( 638 isVerticalContentView, false /*isBiometricAllowed*/); 639 mInstrumentation.waitForIdleSync(); 640 641 if (shouldShow) { 642 if (shouldShowBpWithoutIconForCredential) { 643 waitForState(STATE_AUTH_STARTED_UI_SHOWING); 644 findAndPressButton(BUTTON_ID_USE_CREDENTIAL); 645 waitForState(STATE_SHOWING_DEVICE_CREDENTIAL); 646 } 647 // Wait for any animations to complete. Ideally, this should be reflected in 648 // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different 649 // processes so we'd need to add some additional plumbing. We can improve this in the 650 // future. 651 // TODO(b/152240892) 652 Thread.sleep(1000); 653 waitForState(STATE_SHOWING_DEVICE_CREDENTIAL); 654 BiometricServiceState state = getCurrentState(); 655 assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState); 656 } else { 657 Utils.waitForIdleService(this::getSensorStates); 658 } 659 } 660 } 661