1 /*
2  * Copyright (C) 2023 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.systemui.biometrics;
18 
19 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FACE_REENROLL_DIALOG;
20 import static com.android.systemui.biometrics.BiometricNotificationBroadcastReceiver.ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.ArgumentMatchers.eq;
26 import static org.mockito.Mockito.never;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.when;
29 
30 import android.app.Notification;
31 import android.app.NotificationManager;
32 import android.hardware.biometrics.BiometricFaceConstants;
33 import android.hardware.biometrics.BiometricSourceType;
34 import android.hardware.biometrics.BiometricStateListener;
35 import android.hardware.face.FaceManager;
36 import android.hardware.fingerprint.FingerprintManager;
37 import android.os.Handler;
38 import android.os.UserHandle;
39 import android.testing.AndroidTestingRunner;
40 import android.testing.TestableLooper;
41 
42 import androidx.test.filters.SmallTest;
43 
44 import com.android.keyguard.KeyguardUpdateMonitor;
45 import com.android.keyguard.KeyguardUpdateMonitorCallback;
46 import com.android.systemui.SysuiTestCase;
47 import com.android.systemui.statusbar.policy.KeyguardStateController;
48 
49 import org.junit.Before;
50 import org.junit.Rule;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.ArgumentCaptor;
54 import org.mockito.Mock;
55 import org.mockito.junit.MockitoJUnit;
56 import org.mockito.junit.MockitoRule;
57 
58 import java.util.Optional;
59 
60 @SmallTest
61 @RunWith(AndroidTestingRunner.class)
62 @TestableLooper.RunWithLooper(setAsMainLooper = true)
63 public class BiometricNotificationServiceTest extends SysuiTestCase {
64     @Rule
65     public MockitoRule rule = MockitoJUnit.rule();
66 
67     @Mock
68     KeyguardUpdateMonitor mKeyguardUpdateMonitor;
69     @Mock
70     KeyguardStateController mKeyguardStateController;
71     @Mock
72     NotificationManager mNotificationManager;
73     @Mock
74     Optional<FingerprintReEnrollNotification> mFingerprintReEnrollNotificationOptional;
75     @Mock
76     FingerprintReEnrollNotification mFingerprintReEnrollNotification;
77     @Mock
78     FingerprintManager mFingerprintManager;
79     @Mock
80     FaceManager mFaceManager;
81 
82     private static final String TAG = "BiometricNotificationService";
83     private static final int FACE_NOTIFICATION_ID = 1;
84     private static final int FINGERPRINT_NOTIFICATION_ID = 2;
85     private static final long SHOW_NOTIFICATION_DELAY_MS = 5_000L; // 5 seconds
86     private static final int FINGERPRINT_ACQUIRED_RE_ENROLL = 0;
87 
88     private final ArgumentCaptor<Notification> mNotificationArgumentCaptor =
89             ArgumentCaptor.forClass(Notification.class);
90     private BiometricNotificationService mBiometricNotificationService;
91     private TestableLooper mLooper;
92     private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
93     private KeyguardStateController.Callback mKeyguardStateControllerCallback;
94     private BiometricStateListener mFaceStateListener;
95     private BiometricStateListener mFingerprintStateListener;
96 
97     @Before
setUp()98     public void setUp() {
99         when(mFingerprintReEnrollNotificationOptional.orElse(any()))
100                 .thenReturn(mFingerprintReEnrollNotification);
101         when(mFingerprintReEnrollNotification.isFingerprintReEnrollRequired(
102                 FINGERPRINT_ACQUIRED_RE_ENROLL)).thenReturn(true);
103 
104         mLooper = TestableLooper.get(this);
105         Handler handler = new Handler(mLooper.getLooper());
106         BiometricNotificationDialogFactory dialogFactory = new BiometricNotificationDialogFactory();
107         BiometricNotificationBroadcastReceiver broadcastReceiver =
108                 new BiometricNotificationBroadcastReceiver(mContext, dialogFactory);
109         mBiometricNotificationService =
110                 new BiometricNotificationService(mContext,
111                         mKeyguardUpdateMonitor, mKeyguardStateController, handler,
112                         mNotificationManager,
113                         broadcastReceiver,
114                         mFingerprintReEnrollNotificationOptional,
115                         mFingerprintManager,
116                         mFaceManager);
117         mBiometricNotificationService.start();
118 
119         ArgumentCaptor<KeyguardUpdateMonitorCallback> updateMonitorCallbackArgumentCaptor =
120                 ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
121         ArgumentCaptor<KeyguardStateController.Callback> stateControllerCallbackArgumentCaptor =
122                 ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
123         ArgumentCaptor<BiometricStateListener> faceStateListenerArgumentCaptor =
124                 ArgumentCaptor.forClass(BiometricStateListener.class);
125         ArgumentCaptor<BiometricStateListener> fingerprintStateListenerArgumentCaptor =
126                 ArgumentCaptor.forClass(BiometricStateListener.class);
127 
128         verify(mKeyguardUpdateMonitor).registerCallback(
129                 updateMonitorCallbackArgumentCaptor.capture());
130         verify(mKeyguardStateController).addCallback(
131                 stateControllerCallbackArgumentCaptor.capture());
132         verify(mFaceManager).registerBiometricStateListener(
133                 faceStateListenerArgumentCaptor.capture());
134         verify(mFingerprintManager).registerBiometricStateListener(
135                 fingerprintStateListenerArgumentCaptor.capture());
136 
137         mFaceStateListener = faceStateListenerArgumentCaptor.getValue();
138         mFingerprintStateListener = fingerprintStateListenerArgumentCaptor.getValue();
139         mKeyguardUpdateMonitorCallback = updateMonitorCallbackArgumentCaptor.getValue();
140         mKeyguardStateControllerCallback = stateControllerCallbackArgumentCaptor.getValue();
141     }
142 
143     @Test
testShowFingerprintReEnrollNotification_onAcquiredReEnroll()144     public void testShowFingerprintReEnrollNotification_onAcquiredReEnroll() {
145         when(mKeyguardStateController.isShowing()).thenReturn(false);
146 
147         mKeyguardUpdateMonitorCallback.onBiometricHelp(
148                 FINGERPRINT_ACQUIRED_RE_ENROLL,
149                 "Testing Fingerprint Re-enrollment" /* errString */,
150                 BiometricSourceType.FINGERPRINT
151         );
152         mKeyguardStateControllerCallback.onKeyguardShowingChanged();
153 
154         mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
155         mLooper.processAllMessages();
156 
157         verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
158                 mNotificationArgumentCaptor.capture(), any());
159 
160         Notification fingerprintNotification = mNotificationArgumentCaptor.getValue();
161 
162         assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
163                 .isEqualTo(ACTION_SHOW_FINGERPRINT_REENROLL_DIALOG);
164     }
165     @Test
testShowFaceReEnrollNotification_onErrorReEnroll()166     public void testShowFaceReEnrollNotification_onErrorReEnroll() {
167         when(mKeyguardStateController.isShowing()).thenReturn(false);
168 
169         mKeyguardUpdateMonitorCallback.onBiometricError(
170                 BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL,
171                 "Testing Face Re-enrollment" /* errString */,
172                 BiometricSourceType.FACE
173         );
174         mKeyguardStateControllerCallback.onKeyguardShowingChanged();
175 
176         mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
177         mLooper.processAllMessages();
178 
179         verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
180                 mNotificationArgumentCaptor.capture(), any());
181 
182         Notification fingerprintNotification = mNotificationArgumentCaptor.getValue();
183 
184         assertThat(fingerprintNotification.contentIntent.getIntent().getAction())
185                 .isEqualTo(ACTION_SHOW_FACE_REENROLL_DIALOG);
186     }
187 
188     @Test
testCancelReEnrollmentNotification_onFaceEnrollmentStateChange()189     public void testCancelReEnrollmentNotification_onFaceEnrollmentStateChange() {
190         when(mKeyguardStateController.isShowing()).thenReturn(false);
191 
192         mKeyguardUpdateMonitorCallback.onBiometricError(
193                 BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL,
194                 "Testing Face Re-enrollment" /* errString */,
195                 BiometricSourceType.FACE
196         );
197         mKeyguardStateControllerCallback.onKeyguardShowingChanged();
198 
199         mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
200         mLooper.processAllMessages();
201 
202         verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
203                 mNotificationArgumentCaptor.capture(), any());
204 
205         mFaceStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */,
206                 false /* hasEnrollments */);
207 
208         verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
209                 eq(UserHandle.CURRENT));
210     }
211 
212     @Test
testCancelReEnrollmentNotification_onFingerprintEnrollmentStateChange()213     public void testCancelReEnrollmentNotification_onFingerprintEnrollmentStateChange() {
214         when(mKeyguardStateController.isShowing()).thenReturn(false);
215 
216         mKeyguardUpdateMonitorCallback.onBiometricHelp(
217                 FINGERPRINT_ACQUIRED_RE_ENROLL,
218                 "Testing Fingerprint Re-enrollment" /* errString */,
219                 BiometricSourceType.FINGERPRINT
220         );
221         mKeyguardStateControllerCallback.onKeyguardShowingChanged();
222 
223         mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
224         mLooper.processAllMessages();
225 
226         verify(mNotificationManager).notifyAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
227                 mNotificationArgumentCaptor.capture(), any());
228 
229         mFingerprintStateListener.onEnrollmentsChanged(0 /* userId */, 0 /* sensorId */,
230                 false /* hasEnrollments */);
231 
232         verify(mNotificationManager).cancelAsUser(eq(TAG), eq(FINGERPRINT_NOTIFICATION_ID),
233                 eq(UserHandle.CURRENT));
234     }
235 
236     @Test
testResetFaceUnlockReEnroll_onStart()237     public void testResetFaceUnlockReEnroll_onStart() {
238         when(mKeyguardStateController.isShowing()).thenReturn(false);
239 
240         mKeyguardUpdateMonitorCallback.onBiometricError(
241                 BiometricFaceConstants.BIOMETRIC_ERROR_RE_ENROLL,
242                 "Testing Face Re-enrollment" /* errString */,
243                 BiometricSourceType.FACE
244         );
245 
246         mBiometricNotificationService.start();
247         mKeyguardStateControllerCallback.onKeyguardShowingChanged();
248 
249         mLooper.moveTimeForward(SHOW_NOTIFICATION_DELAY_MS);
250         mLooper.processAllMessages();
251 
252         verify(mNotificationManager, never()).notifyAsUser(eq(TAG), eq(FACE_NOTIFICATION_ID),
253                 mNotificationArgumentCaptor.capture(), any());
254     }
255 
256 }
257