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