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.SensorStates.SensorState; 21 import static android.server.biometrics.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 org.junit.Assert.assertEquals; 31 import static org.junit.Assert.assertNotNull; 32 import static org.junit.Assert.assertTrue; 33 import static org.junit.Assert.fail; 34 import static org.junit.Assume.assumeTrue; 35 import static org.mockito.Mockito.mock; 36 37 import android.app.Instrumentation; 38 import android.content.ComponentName; 39 import android.content.pm.PackageManager; 40 import android.hardware.biometrics.BiometricManager; 41 import android.hardware.biometrics.BiometricManager.Authenticators; 42 import android.hardware.biometrics.BiometricPrompt; 43 import android.hardware.biometrics.BiometricTestSession; 44 import android.hardware.biometrics.SensorProperties; 45 import android.os.Bundle; 46 import android.os.CancellationSignal; 47 import android.os.Handler; 48 import android.os.Looper; 49 import android.os.PowerManager; 50 import android.server.wm.ActivityManagerTestBase; 51 import android.server.wm.TestJournalProvider.TestJournal; 52 import android.server.wm.UiDeviceUtils; 53 import android.server.wm.WindowManagerState; 54 import android.support.test.uiautomator.By; 55 import android.support.test.uiautomator.UiDevice; 56 import android.support.test.uiautomator.UiObject2; 57 import android.util.Log; 58 59 import androidx.annotation.NonNull; 60 import androidx.annotation.Nullable; 61 62 import com.android.server.biometrics.nano.BiometricServiceStateProto; 63 64 import org.junit.After; 65 import org.junit.Before; 66 67 import java.util.ArrayList; 68 import java.util.Collections; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.concurrent.Executor; 72 73 /** 74 * Base class containing useful functionality. Actual tests should be done in subclasses. 75 */ 76 abstract class BiometricTestBase extends ActivityManagerTestBase { 77 78 private static final String TAG = "BiometricTestBase"; 79 private static final String DUMPSYS_BIOMETRIC = Utils.DUMPSYS_BIOMETRIC; 80 private static final String FLAG_CLEAR_SCHEDULER_LOG = " --clear-scheduler-buffer"; 81 82 // Negative-side (left) buttons 83 protected static final String BUTTON_ID_NEGATIVE = "button_negative"; 84 protected static final String BUTTON_ID_USE_CREDENTIAL = "button_use_credential"; 85 86 // Positive-side (right) buttons 87 protected static final String BUTTON_ID_CONFIRM = "button_confirm"; 88 protected static final String BUTTON_ID_TRY_AGAIN = "button_try_again"; 89 90 // Biometric text contents 91 protected static final String TITLE_VIEW = "title"; 92 protected static final String SUBTITLE_VIEW = "subtitle"; 93 protected static final String DESCRIPTION_VIEW = "description"; 94 95 protected static final String VIEW_ID_PASSWORD_FIELD = "lockPassword"; 96 97 @NonNull protected Instrumentation mInstrumentation; 98 @NonNull protected BiometricManager mBiometricManager; 99 @NonNull protected List<SensorProperties> mSensorProperties; 100 @Nullable private PowerManager.WakeLock mWakeLock; 101 @NonNull protected UiDevice mDevice; 102 protected boolean mHasStrongBox; 103 104 /** 105 * Expose this functionality to our package, since ActivityManagerTestBase's is `protected`. 106 * @param componentName 107 */ launchActivity(@onNull ComponentName componentName)108 void launchActivity(@NonNull ComponentName componentName) { 109 super.launchActivity(componentName); 110 } 111 112 /** @see Utils#getBiometricServiceCurrentState() */ 113 @NonNull getCurrentState()114 protected BiometricServiceState getCurrentState() throws Exception { 115 return Utils.getBiometricServiceCurrentState(); 116 } 117 118 @NonNull getCurrentStateAndClearSchedulerLog()119 protected BiometricServiceState getCurrentStateAndClearSchedulerLog() throws Exception { 120 final byte[] dump = Utils.executeShellCommand(DUMPSYS_BIOMETRIC 121 + FLAG_CLEAR_SCHEDULER_LOG); 122 final BiometricServiceStateProto proto = BiometricServiceStateProto.parseFrom(dump); 123 return BiometricServiceState.parseFrom(proto); 124 } 125 126 @Nullable findView(String id)127 protected UiObject2 findView(String id) { 128 Log.d(TAG, "Finding view: " + id); 129 return mDevice.findObject(By.res(mBiometricManager.getUiPackage(), id)); 130 } 131 findAndPressButton(String id)132 protected void findAndPressButton(String id) { 133 final UiObject2 button = findView(id); 134 assertNotNull(button); 135 Log.d(TAG, "Clicking button: " + id); 136 button.click(); 137 } 138 getSensorStates()139 protected SensorStates getSensorStates() throws Exception { 140 return getCurrentState().mSensorStates; 141 } 142 waitForState(@iometricServiceState.AuthSessionState int state)143 protected void waitForState(@BiometricServiceState.AuthSessionState int state) 144 throws Exception { 145 for (int i = 0; i < 20; i++) { 146 final BiometricServiceState serviceState = getCurrentState(); 147 if (serviceState.mState != state) { 148 Log.d(TAG, "Not in state " + state + " yet, current: " + serviceState.mState); 149 Thread.sleep(300); 150 } else { 151 return; 152 } 153 } 154 Log.d(TAG, "Timed out waiting for state to become: " + state); 155 } 156 waitForStateNotEqual(@iometricServiceState.AuthSessionState int state)157 private void waitForStateNotEqual(@BiometricServiceState.AuthSessionState int state) 158 throws Exception { 159 for (int i = 0; i < 20; i++) { 160 final BiometricServiceState serviceState = getCurrentState(); 161 if (serviceState.mState == state) { 162 Log.d(TAG, "Not out of state yet, current: " + serviceState.mState); 163 Thread.sleep(300); 164 } else { 165 return; 166 } 167 } 168 Log.d(TAG, "Timed out waiting for state to not equal: " + state); 169 } 170 anyEnrollmentsExist()171 private boolean anyEnrollmentsExist() throws Exception { 172 final BiometricServiceState serviceState = getCurrentState(); 173 174 for (SensorState sensorState : serviceState.mSensorStates.sensorStates.values()) { 175 for (UserState userState : sensorState.getUserStates().values()) { 176 if (userState.numEnrolled != 0) { 177 Log.d(TAG, "Enrollments still exist: " + serviceState); 178 return true; 179 } 180 } 181 } 182 return false; 183 } 184 successfullyAuthenticate(@onNull BiometricTestSession session, int userId)185 protected void successfullyAuthenticate(@NonNull BiometricTestSession session, int userId) 186 throws Exception { 187 session.acceptAuthentication(userId); 188 mInstrumentation.waitForIdleSync(); 189 waitForStateNotEqual(STATE_AUTH_STARTED_UI_SHOWING); 190 BiometricServiceState state = getCurrentState(); 191 Log.d(TAG, "State after acceptAuthentication: " + state); 192 if (state.mState == STATE_AUTH_PENDING_CONFIRM) { 193 findAndPressButton(BUTTON_ID_CONFIRM); 194 mInstrumentation.waitForIdleSync(); 195 waitForState(STATE_AUTH_IDLE); 196 } else { 197 waitForState(STATE_AUTH_IDLE); 198 } 199 200 assertEquals("Failed to become idle after authenticating", 201 STATE_AUTH_IDLE, getCurrentState().mState); 202 } 203 successfullyEnterCredential()204 protected void successfullyEnterCredential() throws Exception { 205 waitForState(STATE_SHOWING_DEVICE_CREDENTIAL); 206 BiometricServiceState state = getCurrentState(); 207 assertTrue(state.toString(), state.mSensorStates.areAllSensorsIdle()); 208 assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState); 209 210 // Wait for any animations to complete. Ideally, this should be reflected in 211 // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different processes 212 // so we'd need to add some additional plumbing. We can improve this in the future. 213 // TODO(b/152240892) 214 Thread.sleep(1000); 215 216 // Enter credential. AuthSession done, authentication callback received 217 final UiObject2 passwordField = findView(VIEW_ID_PASSWORD_FIELD); 218 Log.d(TAG, "Focusing, entering, submitting credential"); 219 passwordField.click(); 220 passwordField.setText(LOCK_CREDENTIAL); 221 mDevice.pressEnter(); 222 waitForState(STATE_AUTH_IDLE); 223 224 state = getCurrentState(); 225 assertEquals(state.toString(), STATE_AUTH_IDLE, state.mState); 226 } 227 cancelAuthentication(@onNull CancellationSignal cancel)228 protected void cancelAuthentication(@NonNull CancellationSignal cancel) throws Exception { 229 cancel.cancel(); 230 mInstrumentation.waitForIdleSync(); 231 waitForState(STATE_AUTH_IDLE); 232 233 //TODO(b/152240892): Currently BiometricService does not get a signal from SystemUI 234 // when the dialog finishes animating away. 235 Thread.sleep(1000); 236 237 BiometricServiceState state = getCurrentState(); 238 assertEquals("Not idle after requesting cancellation", state.mState, STATE_AUTH_IDLE); 239 } 240 waitForAllUnenrolled()241 protected void waitForAllUnenrolled() throws Exception { 242 for (int i = 0; i < 20; i++) { 243 if (anyEnrollmentsExist()) { 244 Log.d(TAG, "Enrollments still exist.."); 245 Thread.sleep(300); 246 } else { 247 return; 248 } 249 } 250 fail("Some sensors still have enrollments. State: " + getCurrentState()); 251 } 252 253 /** 254 * Shows a BiometricPrompt that specifies {@link Authenticators#DEVICE_CREDENTIAL}. 255 */ showCredentialOnlyBiometricPrompt( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow)256 protected void showCredentialOnlyBiometricPrompt( 257 @NonNull BiometricPrompt.AuthenticationCallback callback, 258 @NonNull CancellationSignal cancellationSignal, 259 boolean shouldShow) throws Exception { 260 showCredentialOnlyBiometricPromptWithContents(callback, cancellationSignal, shouldShow, 261 "Title", "Subtitle", "Description"); 262 } 263 264 /** 265 * Shows a BiometricPrompt that specifies {@link Authenticators#DEVICE_CREDENTIAL} 266 * and the specified contents. 267 */ showCredentialOnlyBiometricPromptWithContents( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow, @NonNull String title, @NonNull String subtitle, @NonNull String description)268 protected void showCredentialOnlyBiometricPromptWithContents( 269 @NonNull BiometricPrompt.AuthenticationCallback callback, 270 @NonNull CancellationSignal cancellationSignal, boolean shouldShow, 271 @NonNull String title, @NonNull String subtitle, 272 @NonNull String description) throws Exception { 273 final Handler handler = new Handler(Looper.getMainLooper()); 274 final Executor executor = handler::post; 275 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 276 .setTitle(title) 277 .setSubtitle(subtitle) 278 .setDescription(description) 279 .setAllowedAuthenticators(Authenticators.DEVICE_CREDENTIAL) 280 .setAllowBackgroundAuthentication(true) 281 .build(); 282 283 prompt.authenticate(cancellationSignal, executor, callback); 284 mInstrumentation.waitForIdleSync(); 285 286 // Wait for any animations to complete. Ideally, this should be reflected in 287 // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different processes 288 // so we'd need to add some additional plumbing. We can improve this in the future. 289 // TODO(b/152240892) 290 Thread.sleep(1000); 291 292 if (shouldShow) { 293 waitForState(STATE_SHOWING_DEVICE_CREDENTIAL); 294 BiometricServiceState state = getCurrentState(); 295 assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState); 296 } else { 297 Utils.waitForIdleService(this::getSensorStates); 298 } 299 } 300 301 /** 302 * SHows a BiometricPrompt that sets 303 * {@link BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)} to true. 304 */ showDeviceCredentialAllowedBiometricPrompt( @onNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal, boolean shouldShow)305 protected void showDeviceCredentialAllowedBiometricPrompt( 306 @NonNull BiometricPrompt.AuthenticationCallback callback, 307 @NonNull CancellationSignal cancellationSignal, 308 boolean shouldShow) throws Exception { 309 final Handler handler = new Handler(Looper.getMainLooper()); 310 final Executor executor = handler::post; 311 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 312 .setTitle("Title") 313 .setSubtitle("Subtitle") 314 .setDescription("Description") 315 .setDeviceCredentialAllowed(true) 316 .setAllowBackgroundAuthentication(true) 317 .build(); 318 319 prompt.authenticate(cancellationSignal, executor, callback); 320 mInstrumentation.waitForIdleSync(); 321 322 // Wait for any animations to complete. Ideally, this should be reflected in 323 // STATE_SHOWING_DEVICE_CREDENTIAL, but SysUI and BiometricService are different processes 324 // so we'd need to add some additional plumbing. We can improve this in the future. 325 // TODO(b/152240892) 326 Thread.sleep(1000); 327 328 if (shouldShow) { 329 waitForState(STATE_SHOWING_DEVICE_CREDENTIAL); 330 BiometricServiceState state = getCurrentState(); 331 assertEquals(state.toString(), STATE_SHOWING_DEVICE_CREDENTIAL, state.mState); 332 } else { 333 Utils.waitForIdleService(this::getSensorStates); 334 } 335 } 336 showDefaultBiometricPrompt(int sensorId, int userId, boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull CancellationSignal cancellationSignal)337 protected void showDefaultBiometricPrompt(int sensorId, int userId, 338 boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback, 339 @NonNull CancellationSignal cancellationSignal) throws Exception { 340 final Handler handler = new Handler(Looper.getMainLooper()); 341 final Executor executor = handler::post; 342 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 343 .setTitle("Title") 344 .setSubtitle("Subtitle") 345 .setDescription("Description") 346 .setConfirmationRequired(requireConfirmation) 347 .setNegativeButton("Negative Button", executor, (dialog, which) -> { 348 Log.d(TAG, "Negative button pressed"); 349 }) 350 .setAllowBackgroundAuthentication(true) 351 .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId))) 352 .build(); 353 prompt.authenticate(cancellationSignal, executor, callback); 354 355 waitForState(STATE_AUTH_STARTED_UI_SHOWING); 356 } 357 358 /** 359 * Shows the default BiometricPrompt (sensors meeting BIOMETRIC_WEAK) with a negative button, 360 * but does not complete authentication. In other words, the dialog will stay on the screen. 361 */ showDefaultBiometricPromptWithContents(int sensorId, int userId, boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback, @NonNull String title, @NonNull String subtitle, @NonNull String description, @NonNull String negativeButtonText)362 protected void showDefaultBiometricPromptWithContents(int sensorId, int userId, 363 boolean requireConfirmation, @NonNull BiometricPrompt.AuthenticationCallback callback, 364 @NonNull String title, @NonNull String subtitle, @NonNull String description, 365 @NonNull String negativeButtonText) throws Exception { 366 final Handler handler = new Handler(Looper.getMainLooper()); 367 final Executor executor = handler::post; 368 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 369 .setTitle(title) 370 .setSubtitle(subtitle) 371 .setDescription(description) 372 .setConfirmationRequired(requireConfirmation) 373 .setNegativeButton(negativeButtonText, executor, (dialog, which) -> { 374 Log.d(TAG, "Negative button pressed"); 375 }) 376 .setAllowBackgroundAuthentication(true) 377 .setAllowedSensorIds(new ArrayList<>(Collections.singletonList(sensorId))) 378 .build(); 379 prompt.authenticate(new CancellationSignal(), executor, callback); 380 381 waitForState(STATE_AUTH_STARTED_UI_SHOWING); 382 } 383 384 /** 385 * Shows the default BiometricPrompt (sensors meeting BIOMETRIC_WEAK) with a negative button, 386 * and fakes successful authentication via TestApis. 387 */ showDefaultBiometricPromptAndAuth(@onNull BiometricTestSession session, int sensorId, int userId)388 protected void showDefaultBiometricPromptAndAuth(@NonNull BiometricTestSession session, 389 int sensorId, int userId) throws Exception { 390 BiometricPrompt.AuthenticationCallback callback = mock( 391 BiometricPrompt.AuthenticationCallback.class); 392 showDefaultBiometricPromptWithContents(sensorId, userId, false /* requireConfirmation */, 393 callback, "Title", "Subtitle", "Description", "Negative Button"); 394 successfullyAuthenticate(session, userId); 395 } 396 showBiometricPromptWithAuthenticators(int authenticators)397 protected void showBiometricPromptWithAuthenticators(int authenticators) { 398 final Handler handler = new Handler(Looper.getMainLooper()); 399 final Executor executor = handler::post; 400 final BiometricPrompt prompt = new BiometricPrompt.Builder(mContext) 401 .setTitle("Title") 402 .setSubtitle("Subtitle") 403 .setDescription("Description") 404 .setNegativeButton("Negative Button", executor, (dialog, which) -> { 405 Log.d(TAG, "Negative button pressed"); 406 }) 407 .setAllowBackgroundAuthentication(true) 408 .setAllowedAuthenticators(authenticators) 409 .build(); 410 prompt.authenticate(new CancellationSignal(), executor, 411 new BiometricPrompt.AuthenticationCallback() { 412 @Override 413 public void onAuthenticationError(int errorCode, CharSequence errString) { 414 Log.d(TAG, "onAuthenticationError: " + errorCode); 415 } 416 417 @Override 418 public void onAuthenticationSucceeded( 419 BiometricPrompt.AuthenticationResult result) { 420 Log.d(TAG, "onAuthenticationSucceeded"); 421 } 422 }); 423 } 424 launchActivityAndWaitForResumed(@onNull ActivitySession activitySession)425 protected void launchActivityAndWaitForResumed(@NonNull ActivitySession activitySession) { 426 activitySession.start(); 427 mWmState.waitForActivityState(activitySession.getComponentName(), 428 WindowManagerState.STATE_RESUMED); 429 mInstrumentation.waitForIdleSync(); 430 } 431 closeActivity(@onNull ActivitySession activitySession)432 protected void closeActivity(@NonNull ActivitySession activitySession) throws Exception { 433 activitySession.close(); 434 mInstrumentation.waitForIdleSync(); 435 } 436 getCurrentStrength(int sensorId)437 protected int getCurrentStrength(int sensorId) throws Exception { 438 final BiometricServiceState serviceState = getCurrentState(); 439 return serviceState.mSensorStates.sensorStates.get(sensorId).getCurrentStrength(); 440 } 441 getSensorsOfTargetStrength(int targetStrength)442 protected List<Integer> getSensorsOfTargetStrength(int targetStrength) { 443 final List<Integer> sensors = new ArrayList<>(); 444 for (SensorProperties prop : mSensorProperties) { 445 if (prop.getSensorStrength() == targetStrength) { 446 sensors.add(prop.getSensorId()); 447 } 448 } 449 Log.d(TAG, "getSensorsOfTargetStrength: num of target sensors=" + sensors.size()); 450 return sensors; 451 } 452 453 @NonNull getCallbackState(@onNull TestJournal journal)454 protected static BiometricCallbackHelper.State getCallbackState(@NonNull TestJournal journal) { 455 Utils.waitFor("Waiting for authentication callback", 456 () -> journal.extras.containsKey(BiometricCallbackHelper.KEY), 457 (lastResult) -> fail("authentication callback never received - died waiting")); 458 459 final Bundle bundle = journal.extras.getBundle(BiometricCallbackHelper.KEY); 460 final BiometricCallbackHelper.State state = 461 BiometricCallbackHelper.State.fromBundle(bundle); 462 463 // Clear the extras since we want to wait for the journal to sync any new info the next 464 // time it's read 465 journal.extras.clear(); 466 467 return state; 468 } 469 470 @Before setUp()471 public void setUp() throws Exception { 472 mInstrumentation = getInstrumentation(); 473 mBiometricManager = mInstrumentation.getContext().getSystemService(BiometricManager.class); 474 475 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(); 476 mDevice = UiDevice.getInstance(mInstrumentation); 477 mSensorProperties = mBiometricManager.getSensorProperties(); 478 479 assumeTrue(mInstrumentation.getContext().getPackageManager().hasSystemFeature( 480 PackageManager.FEATURE_SECURE_LOCK_SCREEN)); 481 482 mHasStrongBox = mContext.getPackageManager().hasSystemFeature( 483 PackageManager.FEATURE_STRONGBOX_KEYSTORE); 484 485 // Keep the screen on for the duration of each test, since BiometricPrompt goes away 486 // when screen turns off. 487 final PowerManager pm = mInstrumentation.getContext().getSystemService(PowerManager.class); 488 mWakeLock = pm.newWakeLock(FULL_WAKE_LOCK, TAG); 489 mWakeLock.acquire(); 490 491 // Turn screen on and dismiss keyguard 492 UiDeviceUtils.pressWakeupButton(); 493 UiDeviceUtils.pressUnlockButton(); 494 } 495 496 @After cleanup()497 public void cleanup() { 498 mInstrumentation.waitForIdleSync(); 499 500 try { 501 Utils.waitForIdleService(this::getSensorStates); 502 } catch (Exception e) { 503 Log.e(TAG, "Exception when waiting for idle", e); 504 } 505 506 try { 507 final BiometricServiceState state = getCurrentState(); 508 509 for (Map.Entry<Integer, SensorState> sensorEntry 510 : state.mSensorStates.sensorStates.entrySet()) { 511 for (Map.Entry<Integer, UserState> userEntry 512 : sensorEntry.getValue().getUserStates().entrySet()) { 513 if (userEntry.getValue().numEnrolled != 0) { 514 Log.w(TAG, "Cleaning up for sensor: " + sensorEntry.getKey() 515 + ", user: " + userEntry.getKey()); 516 BiometricTestSession session = mBiometricManager.createTestSession( 517 sensorEntry.getKey()); 518 session.cleanupInternalState(userEntry.getKey()); 519 session.close(); 520 } 521 } 522 } 523 } catch (Exception e) { 524 Log.e(TAG, "Unable to get current state in cleanup()"); 525 } 526 527 // Authentication lifecycle is done 528 try { 529 Utils.waitForIdleService(this::getSensorStates); 530 } catch (Exception e) { 531 Log.e(TAG, "Exception when waiting for idle", e); 532 } 533 534 if (mWakeLock != null) { 535 mWakeLock.release(); 536 } 537 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 538 } 539 enrollForSensor(@onNull BiometricTestSession session, int sensorId)540 protected void enrollForSensor(@NonNull BiometricTestSession session, int sensorId) 541 throws Exception { 542 Log.d(TAG, "Enrolling for sensor: " + sensorId); 543 final int userId = 0; 544 545 session.startEnroll(userId); 546 mInstrumentation.waitForIdleSync(); 547 Utils.waitForBusySensor(sensorId, this::getSensorStates); 548 549 session.finishEnroll(userId); 550 mInstrumentation.waitForIdleSync(); 551 Utils.waitForIdleService(this::getSensorStates); 552 553 final BiometricServiceState state = getCurrentState(); 554 assertEquals("Sensor: " + sensorId + " should have exactly one enrollment", 555 1, state.mSensorStates.sensorStates 556 .get(sensorId).getUserStates().get(userId).numEnrolled); 557 } 558 } 559