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