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.keyguard.data.repository
18 
19 import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
20 import android.hardware.biometrics.BiometricSourceType
21 import androidx.test.ext.junit.runners.AndroidJUnit4
22 import androidx.test.filters.SmallTest
23 import com.android.keyguard.KeyguardUpdateMonitor
24 import com.android.keyguard.KeyguardUpdateMonitorCallback
25 import com.android.systemui.RoboPilotTest
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.biometrics.AuthController
28 import com.android.systemui.coroutines.collectLastValue
29 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
30 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
31 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
32 import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
33 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
34 import com.android.systemui.util.mockito.whenever
35 import com.google.common.truth.Truth.assertThat
36 import kotlinx.coroutines.ExperimentalCoroutinesApi
37 import kotlinx.coroutines.test.TestScope
38 import kotlinx.coroutines.test.runCurrent
39 import kotlinx.coroutines.test.runTest
40 import org.junit.Before
41 import org.junit.Test
42 import org.junit.runner.RunWith
43 import org.mockito.ArgumentCaptor
44 import org.mockito.Captor
45 import org.mockito.Mock
46 import org.mockito.Mockito.atLeastOnce
47 import org.mockito.Mockito.verify
48 import org.mockito.MockitoAnnotations
49 
50 @OptIn(ExperimentalCoroutinesApi::class)
51 @SmallTest
52 @RoboPilotTest
53 @RunWith(AndroidJUnit4::class)
54 class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
55     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
56     @Mock private lateinit var authController: AuthController
57     @Captor
58     private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
59 
60     private lateinit var testScope: TestScope
61 
62     private lateinit var underTest: DeviceEntryFingerprintAuthRepository
63 
64     @Before
65     fun setUp() {
66         MockitoAnnotations.initMocks(this)
67         testScope = TestScope()
68 
69         underTest =
70             DeviceEntryFingerprintAuthRepositoryImpl(
71                 authController,
72                 keyguardUpdateMonitor,
73                 testScope.backgroundScope,
74             )
75     }
76 
77     @Test
78     fun isLockedOut_whenFingerprintLockoutStateChanges_emitsNewValue() =
79         testScope.runTest {
80             val isLockedOutValue = collectLastValue(underTest.isLockedOut)
81             runCurrent()
82 
83             verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
84             val callback = updateMonitorCallback.value
85             whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
86 
87             callback.onLockedOutStateChanged(BiometricSourceType.FACE)
88             assertThat(isLockedOutValue()).isFalse()
89 
90             callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
91             assertThat(isLockedOutValue()).isTrue()
92 
93             whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false)
94             callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
95             assertThat(isLockedOutValue()).isFalse()
96         }
97 
98     @Test
99     fun fpRunningStateIsPropagated() =
100         testScope.runTest {
101             val isRunning = collectLastValue(underTest.isRunning)
102             whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true)
103 
104             // Initial value is available
105             assertThat(isRunning()).isTrue()
106 
107             verify(keyguardUpdateMonitor, atLeastOnce())
108                 .registerCallback(updateMonitorCallback.capture())
109             invokeOnCallback {
110                 it.onBiometricRunningStateChanged(false, BiometricSourceType.FINGERPRINT)
111             }
112 
113             assertThat(isRunning()).isFalse()
114 
115             invokeOnCallback { it.onBiometricRunningStateChanged(true, BiometricSourceType.FACE) }
116 
117             assertThat(isRunning()).isFalse()
118 
119             updateMonitorCallback.value.onBiometricRunningStateChanged(
120                 true,
121                 BiometricSourceType.FINGERPRINT
122             )
123             assertThat(isRunning()).isTrue()
124         }
125 
126     private fun invokeOnCallback(action: (KeyguardUpdateMonitorCallback) -> Unit) {
127         updateMonitorCallback.allValues.forEach { action(it) }
128     }
129 
130     @Test
131     fun enabledFingerprintTypeProvidesTheCorrectOutputForSpfs() =
132         testScope.runTest {
133             whenever(authController.isSfpsSupported).thenReturn(true)
134             whenever(authController.isUdfpsSupported).thenReturn(false)
135             whenever(authController.isRearFpsSupported).thenReturn(false)
136 
137             val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
138             assertThat(availableFpSensorType()).isEqualTo(BiometricType.SIDE_FINGERPRINT)
139         }
140 
141     @Test
142     fun enabledFingerprintTypeProvidesTheCorrectOutputForUdfps() =
143         testScope.runTest {
144             whenever(authController.isSfpsSupported).thenReturn(false)
145             whenever(authController.isUdfpsSupported).thenReturn(true)
146             whenever(authController.isRearFpsSupported).thenReturn(false)
147             val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
148             assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
149         }
150 
151     @Test
152     fun enabledFingerprintTypeProvidesTheCorrectOutputForRearFps() =
153         testScope.runTest {
154             whenever(authController.isSfpsSupported).thenReturn(false)
155             whenever(authController.isUdfpsSupported).thenReturn(false)
156             whenever(authController.isRearFpsSupported).thenReturn(true)
157 
158             val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
159 
160             assertThat(availableFpSensorType()).isEqualTo(BiometricType.REAR_FINGERPRINT)
161         }
162 
163     @Test
164     fun enabledFingerprintTypeProvidesTheCorrectOutputAfterAllAuthenticatorsAreRegistered() =
165         testScope.runTest {
166             whenever(authController.isSfpsSupported).thenReturn(false)
167             whenever(authController.isUdfpsSupported).thenReturn(false)
168             whenever(authController.isRearFpsSupported).thenReturn(false)
169             whenever(authController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(false)
170 
171             val availableFpSensorType = collectLastValue(underTest.availableFpSensorType)
172             runCurrent()
173 
174             val callback = ArgumentCaptor.forClass(AuthController.Callback::class.java)
175             verify(authController).addCallback(callback.capture())
176             assertThat(availableFpSensorType()).isNull()
177 
178             whenever(authController.isUdfpsSupported).thenReturn(true)
179             callback.value.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT)
180             assertThat(availableFpSensorType()).isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
181         }
182 
183     @Test
184     fun onFingerprintSuccess_successAuthenticationStatus() =
185         testScope.runTest {
186             val authenticationStatus by collectLastValue(underTest.authenticationStatus)
187             runCurrent()
188 
189             verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
190             updateMonitorCallback.value.onBiometricAuthenticated(
191                 0,
192                 BiometricSourceType.FINGERPRINT,
193                 true,
194             )
195 
196             val status = authenticationStatus as SuccessFingerprintAuthenticationStatus
197             assertThat(status.userId).isEqualTo(0)
198             assertThat(status.isStrongBiometric).isEqualTo(true)
199         }
200 
201     @Test
202     fun onFingerprintFailed_failedAuthenticationStatus() =
203         testScope.runTest {
204             val authenticationStatus by collectLastValue(underTest.authenticationStatus)
205             runCurrent()
206 
207             verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
208             updateMonitorCallback.value.onBiometricAuthFailed(
209                 BiometricSourceType.FINGERPRINT,
210             )
211 
212             assertThat(authenticationStatus)
213                 .isInstanceOf(FailFingerprintAuthenticationStatus::class.java)
214         }
215 
216     @Test
217     fun onFingerprintError_errorAuthenticationStatus() =
218         testScope.runTest {
219             val authenticationStatus by collectLastValue(underTest.authenticationStatus)
220             runCurrent()
221 
222             verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
223             updateMonitorCallback.value.onBiometricError(
224                 1,
225                 "test_string",
226                 BiometricSourceType.FINGERPRINT,
227             )
228 
229             val status = authenticationStatus as ErrorFingerprintAuthenticationStatus
230             assertThat(status.msgId).isEqualTo(1)
231             assertThat(status.msg).isEqualTo("test_string")
232         }
233 
234     @Test
235     fun onFingerprintHelp_helpAuthenticationStatus() =
236         testScope.runTest {
237             val authenticationStatus by collectLastValue(underTest.authenticationStatus)
238             runCurrent()
239 
240             verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
241             updateMonitorCallback.value.onBiometricHelp(
242                 1,
243                 "test_string",
244                 BiometricSourceType.FINGERPRINT,
245             )
246 
247             val status = authenticationStatus as HelpFingerprintAuthenticationStatus
248             assertThat(status.msgId).isEqualTo(1)
249             assertThat(status.msg).isEqualTo("test_string")
250         }
251 
252     @Test
253     fun onFingerprintAcquired_acquiredAuthenticationStatus() =
254         testScope.runTest {
255             val authenticationStatus by collectLastValue(underTest.authenticationStatus)
256             runCurrent()
257 
258             verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
259             updateMonitorCallback.value.onBiometricAcquired(
260                 BiometricSourceType.FINGERPRINT,
261                 5,
262             )
263 
264             val status = authenticationStatus as AcquiredFingerprintAuthenticationStatus
265             assertThat(status.acquiredInfo).isEqualTo(5)
266         }
267 
268     @Test
269     fun onFaceCallbacks_fingerprintAuthenticationStatusIsUnchanged() =
270         testScope.runTest {
271             val authenticationStatus by collectLastValue(underTest.authenticationStatus)
272             runCurrent()
273 
274             verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
275             updateMonitorCallback.value.onBiometricAuthenticated(
276                 0,
277                 BiometricSourceType.FACE,
278                 true,
279             )
280             assertThat(authenticationStatus).isNull()
281 
282             updateMonitorCallback.value.onBiometricAuthFailed(
283                 BiometricSourceType.FACE,
284             )
285             assertThat(authenticationStatus).isNull()
286 
287             updateMonitorCallback.value.onBiometricHelp(
288                 1,
289                 "test_string",
290                 BiometricSourceType.FACE,
291             )
292             assertThat(authenticationStatus).isNull()
293 
294             updateMonitorCallback.value.onBiometricAcquired(
295                 BiometricSourceType.FACE,
296                 5,
297             )
298             assertThat(authenticationStatus).isNull()
299 
300             updateMonitorCallback.value.onBiometricError(
301                 1,
302                 "test_string",
303                 BiometricSourceType.FACE,
304             )
305             assertThat(authenticationStatus).isNull()
306         }
307 }
308