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 com.android.server.biometrics.sensors;
18 
19 import static android.testing.TestableLooper.RunWithLooper;
20 
21 import static junit.framework.Assert.assertTrue;
22 import static junit.framework.Assert.fail;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotEquals;
27 import static org.junit.Assert.assertNull;
28 import static org.mockito.ArgumentMatchers.any;
29 import static org.mockito.ArgumentMatchers.anyInt;
30 import static org.mockito.ArgumentMatchers.eq;
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35 import static org.mockito.Mockito.withSettings;
36 
37 import android.content.Context;
38 import android.hardware.biometrics.BiometricConstants;
39 import android.hardware.biometrics.IBiometricService;
40 import android.os.Binder;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.RemoteException;
44 import android.platform.test.annotations.Presubmit;
45 import android.testing.AndroidTestingRunner;
46 import android.testing.TestableContext;
47 import android.testing.TestableLooper;
48 
49 import androidx.annotation.NonNull;
50 import androidx.annotation.Nullable;
51 import androidx.test.InstrumentationRegistry;
52 import androidx.test.filters.SmallTest;
53 
54 import com.android.server.biometrics.nano.BiometricSchedulerProto;
55 import com.android.server.biometrics.nano.BiometricsProto;
56 
57 import org.junit.Before;
58 import org.junit.Rule;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 import org.mockito.Mock;
62 import org.mockito.MockitoAnnotations;
63 
64 @Presubmit
65 @SmallTest
66 @RunWith(AndroidTestingRunner.class)
67 @RunWithLooper(setAsMainLooper = true)
68 public class BiometricSchedulerTest {
69 
70     private static final String TAG = "BiometricSchedulerTest";
71     private static final int TEST_SENSOR_ID = 1;
72     private static final int LOG_NUM_RECENT_OPERATIONS = 2;
73 
74     private BiometricScheduler mScheduler;
75     private IBinder mToken;
76 
77     @Mock
78     private IBiometricService mBiometricService;
79 
80     @Rule
81     public final TestableContext mContext =
82             new TestableContext(InstrumentationRegistry.getContext(), null);
83 
84     @Before
setUp()85     public void setUp() {
86         MockitoAnnotations.initMocks(this);
87         mToken = new Binder();
88         mScheduler = new BiometricScheduler(TAG, new Handler(TestableLooper.get(this).getLooper()),
89                 BiometricScheduler.SENSOR_TYPE_UNKNOWN, null /* gestureAvailabilityTracker */,
90                 mBiometricService, LOG_NUM_RECENT_OPERATIONS,
91                 CoexCoordinator.getInstance());
92     }
93 
94     @Test
testClientDuplicateFinish_ignoredBySchedulerAndDoesNotCrash()95     public void testClientDuplicateFinish_ignoredBySchedulerAndDoesNotCrash() {
96         final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
97 
98         final HalClientMonitor<Object> client1 =
99                 new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
100         final HalClientMonitor<Object> client2 =
101                 new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
102         mScheduler.scheduleClientMonitor(client1);
103         mScheduler.scheduleClientMonitor(client2);
104 
105         client1.mCallback.onClientFinished(client1, true /* success */);
106         client1.mCallback.onClientFinished(client1, true /* success */);
107     }
108 
109     @Test
testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt()110     public void testRemovesPendingOperations_whenNullHal_andNotBiometricPrompt() {
111         // Even if second client has a non-null daemon, it needs to be canceled.
112         final TestHalClientMonitor client1 = new TestHalClientMonitor(
113                 mContext, mToken, () -> null);
114         final TestHalClientMonitor client2 = new TestHalClientMonitor(
115                 mContext, mToken, () -> mock(Object.class));
116 
117         final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
118         final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
119 
120         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
121         // to pretend like there are two operations in the queue before kicking things off
122         mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
123                 mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
124 
125         mScheduler.scheduleClientMonitor(client1, callback1);
126         assertEquals(1, mScheduler.mPendingOperations.size());
127         // client1 is pending. Allow the scheduler to start once second client is added.
128         mScheduler.mCurrentOperation = null;
129         mScheduler.scheduleClientMonitor(client2, callback2);
130         waitForIdle();
131 
132         assertTrue(client1.mUnableToStart);
133         verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
134         verify(callback1, never()).onClientStarted(any());
135 
136         assertTrue(client2.mUnableToStart);
137         verify(callback2).onClientFinished(eq(client2), eq(false) /* success */);
138         verify(callback2, never()).onClientStarted(any());
139 
140         assertTrue(mScheduler.mPendingOperations.isEmpty());
141     }
142 
143     @Test
testRemovesOnlyBiometricPromptOperation_whenNullHal()144     public void testRemovesOnlyBiometricPromptOperation_whenNullHal() throws Exception {
145         // Second non-BiometricPrompt client has a valid daemon
146         final Object daemon2 = mock(Object.class);
147 
148         final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class);
149 
150         final TestAuthenticationClient client1 =
151                 new TestAuthenticationClient(mContext, () -> null, mToken, listener1);
152         final TestHalClientMonitor client2 =
153                 new TestHalClientMonitor(mContext, mToken, () -> daemon2);
154 
155         final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
156         final BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
157 
158         // Pretend the scheduler is busy so the first operation doesn't start right away. We want
159         // to pretend like there are two operations in the queue before kicking things off
160         mScheduler.mCurrentOperation = new BiometricSchedulerOperation(
161                 mock(BaseClientMonitor.class), mock(BaseClientMonitor.Callback.class));
162 
163         mScheduler.scheduleClientMonitor(client1, callback1);
164         assertEquals(1, mScheduler.mPendingOperations.size());
165         // client1 is pending. Allow the scheduler to start once second client is added.
166         mScheduler.mCurrentOperation = null;
167         mScheduler.scheduleClientMonitor(client2, callback2);
168         waitForIdle();
169 
170         // Simulate that the BiometricPrompt client's sensor is ready
171         mScheduler.startPreparedClient(client1.getCookie());
172 
173         // Client 1 cleans up properly
174         verify(listener1).onError(eq(TEST_SENSOR_ID), anyInt(),
175                 eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0));
176         verify(callback1).onClientFinished(eq(client1), eq(false) /* success */);
177         verify(callback1, never()).onClientStarted(any());
178 
179         // Client 2 was able to start
180         assertFalse(client2.mUnableToStart);
181         assertTrue(client2.mStarted);
182         verify(callback2).onClientStarted(eq(client2));
183     }
184 
185     @Test
testCancelNotInvoked_whenOperationWaitingForCookie()186     public void testCancelNotInvoked_whenOperationWaitingForCookie() {
187         final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class);
188         final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext,
189                 lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class));
190         final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
191 
192         // Schedule a BiometricPrompt authentication request
193         mScheduler.scheduleClientMonitor(client1, callback1);
194 
195         assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
196         assertEquals(client1, mScheduler.mCurrentOperation.getClientMonitor());
197         assertEquals(0, mScheduler.mPendingOperations.size());
198 
199         // Request it to be canceled. The operation can be canceled immediately, and the scheduler
200         // should go back to idle, since in this case the framework has not even requested the HAL
201         // to authenticate yet.
202         mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
203         waitForIdle();
204         assertTrue(client1.isAlreadyDone());
205         assertTrue(client1.mDestroyed);
206         assertFalse(client1.mStartedHal);
207         assertNull(mScheduler.mCurrentOperation);
208     }
209 
210     @Test
testProtoDump_singleCurrentOperation()211     public void testProtoDump_singleCurrentOperation() throws Exception {
212         // Nothing so far
213         BiometricSchedulerProto bsp = getDump(true /* clearSchedulerBuffer */);
214         assertEquals(BiometricsProto.CM_NONE, bsp.currentOperation);
215         assertEquals(0, bsp.totalOperations);
216         // TODO:(b/178828362) See bug and/or commit message :/
217         // assertEquals(0, bsp.recentOperations.length);
218 
219         // Pretend the scheduler is busy enrolling, and check the proto dump again.
220         final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
221                 () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
222         mScheduler.scheduleClientMonitor(client);
223         waitForIdle();
224         bsp = getDump(true /* clearSchedulerBuffer */);
225         assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation);
226         // No operations have completed yet
227         assertEquals(0, bsp.totalOperations);
228 
229         // TODO:(b/178828362) See bug and/or commit message :/
230         assertEquals(1, bsp.recentOperations.length);
231         assertEquals(BiometricsProto.CM_NONE, bsp.recentOperations[0]);
232 
233         // Finish this operation, so the next scheduled one can start
234         client.getCallback().onClientFinished(client, true);
235     }
236 
237     @Test
testProtoDump_fifo()238     public void testProtoDump_fifo() throws Exception {
239         // Add the first operation
240         final TestHalClientMonitor client = new TestHalClientMonitor(mContext, mToken,
241                 () -> mock(Object.class), 0, BiometricsProto.CM_ENROLL);
242         mScheduler.scheduleClientMonitor(client);
243         waitForIdle();
244         BiometricSchedulerProto bsp = getDump(false /* clearSchedulerBuffer */);
245         assertEquals(BiometricsProto.CM_ENROLL, bsp.currentOperation);
246         // No operations have completed yet
247         assertEquals(0, bsp.totalOperations);
248         // TODO:(b/178828362) See bug and/or commit message :/
249         // assertEquals(0, bsp.recentOperations.length);
250         // Finish this operation, so the next scheduled one can start
251         client.getCallback().onClientFinished(client, true);
252 
253         // Add another operation
254         final TestHalClientMonitor client2 = new TestHalClientMonitor(mContext, mToken,
255                 () -> mock(Object.class), 0, BiometricsProto.CM_REMOVE);
256         mScheduler.scheduleClientMonitor(client2);
257         waitForIdle();
258         bsp = getDump(false /* clearSchedulerBuffer */);
259         assertEquals(BiometricsProto.CM_REMOVE, bsp.currentOperation);
260         assertEquals(1, bsp.totalOperations); // Enroll finished
261         assertEquals(1, bsp.recentOperations.length);
262         assertEquals(BiometricsProto.CM_ENROLL, bsp.recentOperations[0]);
263         client2.getCallback().onClientFinished(client2, true);
264 
265         // And another operation
266         final TestHalClientMonitor client3 = new TestHalClientMonitor(mContext, mToken,
267                 () -> mock(Object.class), 0, BiometricsProto.CM_AUTHENTICATE);
268         mScheduler.scheduleClientMonitor(client3);
269         waitForIdle();
270         bsp = getDump(false /* clearSchedulerBuffer */);
271         assertEquals(BiometricsProto.CM_AUTHENTICATE, bsp.currentOperation);
272         assertEquals(2, bsp.totalOperations);
273         assertEquals(2, bsp.recentOperations.length);
274         assertEquals(BiometricsProto.CM_ENROLL, bsp.recentOperations[0]);
275         assertEquals(BiometricsProto.CM_REMOVE, bsp.recentOperations[1]);
276 
277         // Finish the last operation, and check that the first operation is removed from the FIFO.
278         // The test initializes the scheduler with "LOG_NUM_RECENT_OPERATIONS = 2" :)
279         client3.getCallback().onClientFinished(client3, true);
280         waitForIdle();
281         bsp = getDump(true /* clearSchedulerBuffer */);
282         assertEquals(3, bsp.totalOperations);
283         assertEquals(2, bsp.recentOperations.length);
284         assertEquals(BiometricsProto.CM_REMOVE, bsp.recentOperations[0]);
285         assertEquals(BiometricsProto.CM_AUTHENTICATE, bsp.recentOperations[1]);
286         // Nothing is currently running anymore
287         assertEquals(BiometricsProto.CM_NONE, bsp.currentOperation);
288 
289         // RecentOperations queue is cleared (by the previous dump)
290         bsp = getDump(true /* clearSchedulerBuffer */);
291 
292         // TODO:(b/178828362) See bug and/or commit message :/
293         assertEquals(1, bsp.recentOperations.length);
294         assertEquals(BiometricsProto.CM_NONE, bsp.recentOperations[0]);
295     }
296 
297     @Test
testCancelPendingAuth()298     public void testCancelPendingAuth() throws RemoteException {
299         final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
300         final TestHalClientMonitor client1 = new TestHalClientMonitor(mContext, mToken, lazyDaemon);
301         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
302         final TestAuthenticationClient client2 = new TestAuthenticationClient(mContext, lazyDaemon,
303                 mToken, callback);
304 
305         // Add a non-cancellable client, then add the auth client
306         mScheduler.scheduleClientMonitor(client1);
307         mScheduler.scheduleClientMonitor(client2);
308         waitForIdle();
309 
310         assertEquals(mScheduler.getCurrentClient(), client1);
311         assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
312 
313         // Request cancel before the authentication client has started
314         mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
315         waitForIdle();
316         assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
317 
318         // Finish the blocking client. The authentication client should send ERROR_CANCELED
319         client1.getCallback().onClientFinished(client1, true /* success */);
320         waitForIdle();
321         verify(callback).onError(anyInt(), anyInt(),
322                 eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED),
323                 eq(0) /* vendorCode */);
324         assertNull(mScheduler.getCurrentClient());
325         assertTrue(client1.isAlreadyDone());
326         assertTrue(client1.mDestroyed);
327         assertTrue(client2.isAlreadyDone());
328         assertTrue(client2.mDestroyed);
329     }
330 
331     @Test
testCancels_whenAuthRequestIdNotSet()332     public void testCancels_whenAuthRequestIdNotSet() {
333         testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, true /* started */);
334     }
335 
336     @Test
testCancels_whenAuthRequestIdNotSet_notStarted()337     public void testCancels_whenAuthRequestIdNotSet_notStarted() {
338         testCancelsAuthDetectWhenRequestId(null /* requestId */, 2, false /* started */);
339     }
340 
341     @Test
testCancels_whenAuthRequestIdMatches()342     public void testCancels_whenAuthRequestIdMatches() {
343         testCancelsAuthDetectWhenRequestId(200L, 200, true /* started */);
344     }
345 
346     @Test
testCancels_whenAuthRequestIdMatches_noStarted()347     public void testCancels_whenAuthRequestIdMatches_noStarted() {
348         testCancelsAuthDetectWhenRequestId(200L, 200, false /* started */);
349     }
350 
351     @Test
testDoesNotCancel_whenAuthRequestIdMismatched()352     public void testDoesNotCancel_whenAuthRequestIdMismatched() {
353         testCancelsAuthDetectWhenRequestId(10L, 20, true /* started */);
354     }
355 
356     @Test
testDoesNotCancel_whenAuthRequestIdMismatched_notStarted()357     public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
358         testCancelsAuthDetectWhenRequestId(10L, 20, false /* started */);
359     }
360 
testCancelsAuthDetectWhenRequestId(@ullable Long requestId, long cancelRequestId, boolean started)361     private void testCancelsAuthDetectWhenRequestId(@Nullable Long requestId, long cancelRequestId,
362             boolean started) {
363         final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
364         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
365         testCancelsWhenRequestId(requestId, cancelRequestId, started,
366                 new TestAuthenticationClient(mContext, lazyDaemon, mToken, callback));
367     }
368 
369     @Test
testCancels_whenEnrollRequestIdNotSet()370     public void testCancels_whenEnrollRequestIdNotSet() {
371         testCancelsEnrollWhenRequestId(null /* requestId */, 2, false /* started */);
372     }
373 
374     @Test
testCancels_whenEnrollRequestIdMatches()375     public void testCancels_whenEnrollRequestIdMatches() {
376         testCancelsEnrollWhenRequestId(200L, 200, false /* started */);
377     }
378 
379     @Test
testDoesNotCancel_whenEnrollRequestIdMismatched()380     public void testDoesNotCancel_whenEnrollRequestIdMismatched() {
381         testCancelsEnrollWhenRequestId(10L, 20, false /* started */);
382     }
383 
testCancelsEnrollWhenRequestId(@ullable Long requestId, long cancelRequestId, boolean started)384     private void testCancelsEnrollWhenRequestId(@Nullable Long requestId, long cancelRequestId,
385             boolean started) {
386         final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
387         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
388         testCancelsWhenRequestId(requestId, cancelRequestId, started,
389                 new TestEnrollClient(mContext, lazyDaemon, mToken, callback));
390     }
391 
testCancelsWhenRequestId(@ullable Long requestId, long cancelRequestId, boolean started, HalClientMonitor<?> client)392     private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
393             boolean started, HalClientMonitor<?> client) {
394         final boolean matches = requestId == null || requestId == cancelRequestId;
395         if (requestId != null) {
396             client.setRequestId(requestId);
397         }
398 
399         final boolean isAuth = client instanceof TestAuthenticationClient;
400         final boolean isEnroll = client instanceof TestEnrollClient;
401 
402         mScheduler.scheduleClientMonitor(client);
403         if (started) {
404             mScheduler.startPreparedClient(client.getCookie());
405         }
406         waitForIdle();
407         if (isAuth) {
408             mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
409         } else if (isEnroll) {
410             mScheduler.cancelEnrollment(mToken, cancelRequestId);
411         } else {
412             fail("unexpected operation type");
413         }
414         waitForIdle();
415 
416         if (isAuth) {
417             // auth clients that were waiting for cookie when canceled should never invoke the hal
418             final TestAuthenticationClient authClient = (TestAuthenticationClient) client;
419             assertEquals(matches && started ? 1 : 0, authClient.mNumCancels);
420             assertEquals(started, authClient.mStartedHal);
421         } else if (isEnroll) {
422             final TestEnrollClient enrollClient = (TestEnrollClient) client;
423             assertEquals(matches ? 1 : 0, enrollClient.mNumCancels);
424             assertTrue(enrollClient.mStartedHal);
425         }
426 
427         if (matches) {
428             if (started || isEnroll) { // prep'd auth clients and enroll clients
429                 assertTrue(mScheduler.mCurrentOperation.isCanceling());
430             }
431         } else {
432             if (started || isEnroll) { // prep'd auth clients and enroll clients
433                 assertTrue(mScheduler.mCurrentOperation.isStarted());
434             } else {
435                 assertNotEquals(0, mScheduler.mCurrentOperation.isReadyToStart());
436             }
437         }
438     }
439 
440     @Test
testCancelsPending_whenAuthRequestIdsSet()441     public void testCancelsPending_whenAuthRequestIdsSet() {
442         final long requestId1 = 10;
443         final long requestId2 = 20;
444         final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
445         final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
446         final TestAuthenticationClient client1 = new TestAuthenticationClient(
447                 mContext, lazyDaemon, mToken, callback);
448         client1.setRequestId(requestId1);
449         final TestAuthenticationClient client2 = new TestAuthenticationClient(
450                 mContext, lazyDaemon, mToken, callback);
451         client2.setRequestId(requestId2);
452 
453         mScheduler.scheduleClientMonitor(client1);
454         mScheduler.scheduleClientMonitor(client2);
455         mScheduler.startPreparedClient(client1.getCookie());
456         waitForIdle();
457         mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
458         waitForIdle();
459 
460         assertTrue(mScheduler.mCurrentOperation.isStarted());
461         assertFalse(mScheduler.mPendingOperations.getFirst().isStarted());
462 
463         mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
464         waitForIdle();
465 
466         assertTrue(mScheduler.mCurrentOperation.isStarted());
467         assertTrue(mScheduler.mPendingOperations.getFirst().isMarkedCanceling());
468     }
469 
470     @Test
testInterruptPrecedingClients_whenExpected()471     public void testInterruptPrecedingClients_whenExpected() {
472         final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
473                 withSettings().extraInterfaces(Interruptable.class));
474 
475         final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
476         when(interrupter.interruptsPrecedingClients()).thenReturn(true);
477 
478         mScheduler.scheduleClientMonitor(interruptableMonitor);
479         mScheduler.scheduleClientMonitor(interrupter);
480         waitForIdle();
481 
482         verify((Interruptable) interruptableMonitor).cancel();
483         mScheduler.getInternalCallback().onClientFinished(interruptableMonitor, true /* success */);
484     }
485 
486     @Test
testDoesNotInterruptPrecedingClients_whenNotExpected()487     public void testDoesNotInterruptPrecedingClients_whenNotExpected() {
488         final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
489                 withSettings().extraInterfaces(Interruptable.class));
490 
491         final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
492         when(interrupter.interruptsPrecedingClients()).thenReturn(false);
493 
494         mScheduler.scheduleClientMonitor(interruptableMonitor);
495         mScheduler.scheduleClientMonitor(interrupter);
496         waitForIdle();
497 
498         verify((Interruptable) interruptableMonitor, never()).cancel();
499     }
500 
501     @Test
testClientDestroyed_afterFinish()502     public void testClientDestroyed_afterFinish() {
503         final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
504         final TestHalClientMonitor client =
505                 new TestHalClientMonitor(mContext, mToken, nonNullDaemon);
506         mScheduler.scheduleClientMonitor(client);
507         client.mCallback.onClientFinished(client, true /* success */);
508         waitForIdle();
509         assertTrue(client.mDestroyed);
510     }
511 
getDump(boolean clearSchedulerBuffer)512     private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
513         return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
514     }
515 
516     private static class TestAuthenticationClient extends AuthenticationClient<Object> {
517         boolean mStartedHal = false;
518         boolean mStoppedHal = false;
519         boolean mDestroyed = false;
520         int mNumCancels = 0;
521 
TestAuthenticationClient(@onNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener)522         public TestAuthenticationClient(@NonNull Context context,
523                 @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
524                 @NonNull ClientMonitorCallbackConverter listener) {
525             super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */,
526                     false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
527                     TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
528                     0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
529                     false /* isKeyguard */, true /* shouldVibrate */,
530                     false /* isKeyguardBypassEnabled */);
531         }
532 
533         @Override
stopHalOperation()534         protected void stopHalOperation() {
535             mStoppedHal = true;
536         }
537 
538         @Override
startHalOperation()539         protected void startHalOperation() {
540             mStartedHal = true;
541         }
542 
543         @Override
handleLifecycleAfterAuth(boolean authenticated)544         protected void handleLifecycleAfterAuth(boolean authenticated) {}
545 
546         @Override
wasUserDetected()547         public boolean wasUserDetected() {
548             return false;
549         }
550 
551         @Override
destroy()552         public void destroy() {
553             mDestroyed = true;
554             super.destroy();
555         }
556 
557         @Override
cancel()558         public void cancel() {
559             mNumCancels++;
560             super.cancel();
561         }
562     }
563 
564     private static class TestEnrollClient extends EnrollClient<Object> {
565         boolean mStartedHal = false;
566         boolean mStoppedHal = false;
567         int mNumCancels = 0;
568 
TestEnrollClient(@onNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener)569         TestEnrollClient(@NonNull Context context,
570                 @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
571                 @NonNull ClientMonitorCallbackConverter listener) {
572             super(context, lazyDaemon, token, listener, 0 /* userId */, new byte[69],
573                     "test" /* owner */, mock(BiometricUtils.class),
574                     5 /* timeoutSec */, 0 /* statsModality */, TEST_SENSOR_ID,
575                     true /* shouldVibrate */);
576         }
577 
578         @Override
stopHalOperation()579         protected void stopHalOperation() {
580             mStoppedHal = true;
581         }
582 
583         @Override
startHalOperation()584         protected void startHalOperation() {
585             mStartedHal = true;
586         }
587 
588         @Override
hasReachedEnrollmentLimit()589         protected boolean hasReachedEnrollmentLimit() {
590             return false;
591         }
592 
593         @Override
cancel()594         public void cancel() {
595             mNumCancels++;
596             super.cancel();
597         }
598     }
599 
600     private static class TestHalClientMonitor extends HalClientMonitor<Object> {
601         private final int mProtoEnum;
602         private boolean mUnableToStart;
603         private boolean mStarted;
604         private boolean mDestroyed;
605 
TestHalClientMonitor(@onNull Context context, @NonNull IBinder token, @NonNull LazyDaemon<Object> lazyDaemon)606         TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
607                 @NonNull LazyDaemon<Object> lazyDaemon) {
608             this(context, token, lazyDaemon, 0 /* cookie */, BiometricsProto.CM_UPDATE_ACTIVE_USER);
609         }
610 
TestHalClientMonitor(@onNull Context context, @NonNull IBinder token, @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum)611         TestHalClientMonitor(@NonNull Context context, @NonNull IBinder token,
612                 @NonNull LazyDaemon<Object> lazyDaemon, int cookie, int protoEnum) {
613             super(context, lazyDaemon, token /* token */, null /* listener */, 0 /* userId */,
614                     TAG, cookie, TEST_SENSOR_ID, 0 /* statsModality */,
615                     0 /* statsAction */, 0 /* statsClient */);
616             mProtoEnum = protoEnum;
617         }
618 
619         @Override
unableToStart()620         public void unableToStart() {
621             assertFalse(mUnableToStart);
622             mUnableToStart = true;
623         }
624 
625         @Override
getProtoEnum()626         public int getProtoEnum() {
627             return mProtoEnum;
628         }
629 
630         @Override
start(@onNull Callback callback)631         public void start(@NonNull Callback callback) {
632             super.start(callback);
633             assertFalse(mStarted);
634             mStarted = true;
635         }
636 
637         @Override
startHalOperation()638         protected void startHalOperation() {
639             mStarted = true;
640         }
641 
642         @Override
destroy()643         public void destroy() {
644             super.destroy();
645             mDestroyed = true;
646         }
647     }
648 
waitForIdle()649     private void waitForIdle() {
650         TestableLooper.get(this).processAllMessages();
651     }
652 }
653