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.bouncer.data.repo
18 
19 import android.content.pm.UserInfo
20 import android.hardware.biometrics.BiometricSourceType
21 import android.testing.TestableLooper
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import androidx.test.filters.SmallTest
24 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
25 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
26 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
27 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
28 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
29 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
30 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
31 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
32 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
33 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
34 import com.android.keyguard.KeyguardSecurityModel
35 import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
36 import com.android.keyguard.KeyguardUpdateMonitor
37 import com.android.keyguard.KeyguardUpdateMonitorCallback
38 import com.android.systemui.R
39 import com.android.systemui.R.string.keyguard_enter_pin
40 import com.android.systemui.R.string.kg_prompt_after_dpm_lock
41 import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
42 import com.android.systemui.R.string.kg_prompt_auth_timeout
43 import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
44 import com.android.systemui.R.string.kg_prompt_reason_restart_pin
45 import com.android.systemui.R.string.kg_prompt_unattended_update
46 import com.android.systemui.R.string.kg_trust_agent_disabled
47 import com.android.systemui.SysuiTestCase
48 import com.android.systemui.bouncer.data.factory.BouncerMessageFactory
49 import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
50 import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
51 import com.android.systemui.bouncer.shared.model.BouncerMessageModel
52 import com.android.systemui.bouncer.shared.model.Message
53 import com.android.systemui.coroutines.collectLastValue
54 import com.android.systemui.flags.SystemPropertiesHelper
55 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
56 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
57 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
58 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
59 import com.android.systemui.user.data.repository.FakeUserRepository
60 import com.android.systemui.util.mockito.whenever
61 import com.google.common.truth.Truth.assertThat
62 import kotlinx.coroutines.ExperimentalCoroutinesApi
63 import kotlinx.coroutines.test.TestScope
64 import kotlinx.coroutines.test.runCurrent
65 import kotlinx.coroutines.test.runTest
66 import org.junit.Before
67 import org.junit.Test
68 import org.junit.runner.RunWith
69 import org.mockito.ArgumentCaptor
70 import org.mockito.Captor
71 import org.mockito.Mock
72 import org.mockito.Mockito.verify
73 import org.mockito.MockitoAnnotations
74 
75 @OptIn(ExperimentalCoroutinesApi::class)
76 @SmallTest
77 @TestableLooper.RunWithLooper(setAsMainLooper = true)
78 @RunWith(AndroidJUnit4::class)
79 class BouncerMessageRepositoryTest : SysuiTestCase() {
80 
81     @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
82     @Mock private lateinit var securityModel: KeyguardSecurityModel
83     @Mock private lateinit var systemPropertiesHelper: SystemPropertiesHelper
84     @Captor
85     private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
86 
87     private lateinit var underTest: BouncerMessageRepository
88     private lateinit var trustRepository: FakeTrustRepository
89     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
90     private lateinit var userRepository: FakeUserRepository
91     private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
92     private lateinit var testScope: TestScope
93 
94     @Before
95     fun setUp() {
96         MockitoAnnotations.initMocks(this)
97         trustRepository = FakeTrustRepository()
98         biometricSettingsRepository = FakeBiometricSettingsRepository()
99         userRepository = FakeUserRepository()
100         userRepository.setUserInfos(listOf(PRIMARY_USER))
101         fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
102         testScope = TestScope()
103 
104         biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(false)
105         whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
106         underTest =
107             BouncerMessageRepositoryImpl(
108                 trustRepository = trustRepository,
109                 biometricSettingsRepository = biometricSettingsRepository,
110                 updateMonitor = updateMonitor,
111                 bouncerMessageFactory =
112                     BouncerMessageFactory(biometricSettingsRepository, securityModel),
113                 userRepository = userRepository,
114                 fingerprintAuthRepository = fingerprintRepository,
115                 systemPropertiesHelper = systemPropertiesHelper
116             )
117     }
118 
119     @Test
120     fun setCustomMessage_propagatesState() =
121         testScope.runTest {
122             underTest.setCustomMessage(message("not empty"))
123 
124             val customMessage = collectLastValue(underTest.customMessage)
125 
126             assertThat(customMessage()).isEqualTo(message("not empty"))
127         }
128 
129     @Test
130     fun setFaceMessage_propagatesState() =
131         testScope.runTest {
132             underTest.setFaceAcquisitionMessage(message("not empty"))
133 
134             val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
135 
136             assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
137         }
138 
139     @Test
140     fun setFpMessage_propagatesState() =
141         testScope.runTest {
142             underTest.setFingerprintAcquisitionMessage(message("not empty"))
143 
144             val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
145 
146             assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
147         }
148 
149     @Test
150     fun setPrimaryAuthMessage_propagatesState() =
151         testScope.runTest {
152             underTest.setPrimaryAuthMessage(message("not empty"))
153 
154             val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
155 
156             assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
157         }
158 
159     @Test
160     fun biometricAuthMessage_propagatesBiometricAuthMessages() =
161         testScope.runTest {
162             userRepository.setSelectedUserInfo(PRIMARY_USER)
163             val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
164             runCurrent()
165 
166             verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
167 
168             updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
169 
170             assertThat(biometricAuthMessage())
171                 .isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
172 
173             updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
174 
175             assertThat(biometricAuthMessage())
176                 .isEqualTo(
177                     message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
178                 )
179 
180             updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
181 
182             assertThat(biometricAuthMessage()).isNull()
183         }
184 
185     @Test
186     fun onFaceLockout_propagatesState() =
187         testScope.runTest {
188             userRepository.setSelectedUserInfo(PRIMARY_USER)
189             val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
190             runCurrent()
191             verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
192 
193             whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
194             updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
195 
196             assertThat(lockoutMessage())
197                 .isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
198 
199             whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
200             updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
201             assertThat(lockoutMessage()).isNull()
202         }
203 
204     @Test
205     fun onFingerprintLockout_propagatesState() =
206         testScope.runTest {
207             userRepository.setSelectedUserInfo(PRIMARY_USER)
208             val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
209             runCurrent()
210 
211             fingerprintRepository.setLockedOut(true)
212 
213             assertThat(lockedOutMessage())
214                 .isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
215 
216             fingerprintRepository.setLockedOut(false)
217             assertThat(lockedOutMessage()).isNull()
218         }
219 
220     @Test
221     fun onRestartForMainlineUpdate_shouldProvideRelevantMessage() =
222         testScope.runTest {
223             whenever(systemPropertiesHelper.get("sys.boot.reason.last"))
224                 .thenReturn("reboot,mainline_update")
225             userRepository.setSelectedUserInfo(PRIMARY_USER)
226             biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
227 
228             verifyMessagesForAuthFlag(
229                 STRONG_AUTH_REQUIRED_AFTER_BOOT to
230                     Pair(keyguard_enter_pin, R.string.kg_prompt_after_update_pin),
231             )
232         }
233 
234     @Test
235     fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
236         testScope.runTest {
237             userRepository.setSelectedUserInfo(PRIMARY_USER)
238             trustRepository.setCurrentUserTrustManaged(false)
239             biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
240             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
241 
242             verifyMessagesForAuthFlag(
243                 STRONG_AUTH_NOT_REQUIRED to null,
244                 STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
245                 SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
246                 STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
247                 STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
248                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
249                 STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
250                 SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
251                 STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
252                 STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
253                     Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
254             )
255         }
256 
257     @Test
258     fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
259         testScope.runTest {
260             userRepository.setSelectedUserInfo(PRIMARY_USER)
261             biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
262             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
263 
264             trustRepository.setCurrentUserTrustManaged(true)
265 
266             verifyMessagesForAuthFlag(
267                 STRONG_AUTH_NOT_REQUIRED to null,
268                 STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
269                 STRONG_AUTH_REQUIRED_AFTER_BOOT to
270                     Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
271                 STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
272                     Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
273                 STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
274                     Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
275                 SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
276                     Pair(keyguard_enter_pin, kg_trust_agent_disabled),
277                 SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
278                     Pair(keyguard_enter_pin, kg_trust_agent_disabled),
279                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
280                     Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
281                 STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
282                     Pair(keyguard_enter_pin, kg_prompt_unattended_update),
283                 STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
284                     Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
285             )
286         }
287 
288     @Test
289     fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
290         testScope.runTest {
291             userRepository.setSelectedUserInfo(PRIMARY_USER)
292             trustRepository.setCurrentUserTrustManaged(false)
293             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false)
294 
295             biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true)
296 
297             verifyMessagesForAuthFlag(
298                 STRONG_AUTH_NOT_REQUIRED to null,
299                 STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
300                 SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
301                 SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
302                 STRONG_AUTH_REQUIRED_AFTER_BOOT to
303                     Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
304                 STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
305                     Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
306                 STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
307                     Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
308                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
309                     Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
310                 STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
311                     Pair(keyguard_enter_pin, kg_prompt_unattended_update),
312                 STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
313                     Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
314             )
315         }
316 
317     @Test
318     fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
319         testScope.runTest {
320             userRepository.setSelectedUserInfo(PRIMARY_USER)
321             trustRepository.setCurrentUserTrustManaged(false)
322             biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
323 
324             biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
325 
326             verifyMessagesForAuthFlag(
327                 STRONG_AUTH_NOT_REQUIRED to null,
328                 STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
329                 SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
330                 SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
331                 STRONG_AUTH_REQUIRED_AFTER_BOOT to
332                     Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
333                 STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
334                     Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
335                 STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
336                     Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
337                 STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
338                     Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
339                 STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
340                     Pair(keyguard_enter_pin, kg_prompt_unattended_update),
341                 STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
342                     Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
343             )
344         }
345 
346     private fun TestScope.verifyMessagesForAuthFlag(
347         vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
348     ) {
349         val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
350 
351         authFlagToExpectedMessages.forEach { (flag, messagePair) ->
352             biometricSettingsRepository.setAuthenticationFlags(
353                 AuthenticationFlags(PRIMARY_USER_ID, flag)
354             )
355 
356             assertThat(authFlagsMessage())
357                 .isEqualTo(messagePair?.let { message(it.first, it.second) })
358         }
359     }
360 
361     private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
362         return BouncerMessageModel(
363             message = Message(messageResId = primaryResId, animate = false),
364             secondaryMessage = Message(messageResId = secondaryResId, animate = false)
365         )
366     }
367     private fun message(value: String): BouncerMessageModel {
368         return BouncerMessageModel(message = Message(message = value))
369     }
370 
371     companion object {
372         private const val PRIMARY_USER_ID = 0
373         private val PRIMARY_USER =
374             UserInfo(
375                 /* id= */ PRIMARY_USER_ID,
376                 /* name= */ "primary user",
377                 /* flags= */ UserInfo.FLAG_PRIMARY
378             )
379     }
380 }
381