1 /* 2 * Copyright 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 @file:OptIn(ExperimentalCoroutinesApi::class) 18 19 package com.android.systemui.authentication.data.repository 20 21 import android.app.admin.DevicePolicyManager 22 import android.content.Intent 23 import android.content.pm.UserInfo 24 import androidx.test.ext.junit.runners.AndroidJUnit4 25 import androidx.test.filters.SmallTest 26 import com.android.internal.widget.LockPatternUtils 27 import com.android.keyguard.KeyguardSecurityModel 28 import com.android.systemui.SysuiTestCase 29 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel 30 import com.android.systemui.coroutines.collectLastValue 31 import com.android.systemui.coroutines.collectValues 32 import com.android.systemui.kosmos.testDispatcher 33 import com.android.systemui.kosmos.testScope 34 import com.android.systemui.log.table.TableLogBuffer 35 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository 36 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy 37 import com.android.systemui.testKosmos 38 import com.android.systemui.user.data.repository.FakeUserRepository 39 import com.android.systemui.util.mockito.whenever 40 import com.android.systemui.util.time.FakeSystemClock 41 import com.google.common.truth.Truth.assertThat 42 import java.util.function.Function 43 import kotlin.time.Duration.Companion.seconds 44 import kotlinx.coroutines.ExperimentalCoroutinesApi 45 import kotlinx.coroutines.runBlocking 46 import kotlinx.coroutines.test.runCurrent 47 import kotlinx.coroutines.test.runTest 48 import org.junit.Before 49 import org.junit.Test 50 import org.junit.runner.RunWith 51 import org.mockito.ArgumentMatchers.anyInt 52 import org.mockito.Mock 53 import org.mockito.MockitoAnnotations 54 55 @SmallTest 56 @RunWith(AndroidJUnit4::class) 57 class AuthenticationRepositoryTest : SysuiTestCase() { 58 59 @Mock private lateinit var lockPatternUtils: LockPatternUtils 60 @Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode> 61 @Mock private lateinit var tableLogger: TableLogBuffer 62 @Mock private lateinit var devicePolicyManager: DevicePolicyManager 63 64 private val kosmos = testKosmos() 65 private val testScope = kosmos.testScope 66 private val clock = FakeSystemClock() 67 private val userRepository = FakeUserRepository() 68 private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository 69 70 private lateinit var underTest: AuthenticationRepository 71 72 private var currentSecurityMode: KeyguardSecurityModel.SecurityMode = 73 KeyguardSecurityModel.SecurityMode.PIN 74 75 @Before setUpnull76 fun setUp() { 77 MockitoAnnotations.initMocks(this) 78 userRepository.setUserInfos(USER_INFOS) 79 runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) } 80 whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode } 81 mobileConnectionsRepository = 82 FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger) 83 84 underTest = 85 AuthenticationRepositoryImpl( 86 applicationScope = testScope.backgroundScope, 87 backgroundDispatcher = kosmos.testDispatcher, 88 clock = clock, 89 getSecurityMode = getSecurityMode, 90 userRepository = userRepository, 91 lockPatternUtils = lockPatternUtils, 92 devicePolicyManager = devicePolicyManager, 93 broadcastDispatcher = fakeBroadcastDispatcher, 94 mobileConnectionsRepository = mobileConnectionsRepository, 95 ) 96 } 97 98 @Test authenticationMethodnull99 fun authenticationMethod() = 100 testScope.runTest { 101 val authMethod by collectLastValue(underTest.authenticationMethod) 102 runCurrent() 103 dispatchBroadcast() 104 assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin) 105 assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin) 106 107 setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.Pattern) 108 assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pattern) 109 assertThat(underTest.getAuthenticationMethod()) 110 .isEqualTo(AuthenticationMethodModel.Pattern) 111 112 setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.None) 113 assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None) 114 assertThat(underTest.getAuthenticationMethod()) 115 .isEqualTo(AuthenticationMethodModel.None) 116 117 currentSecurityMode = KeyguardSecurityModel.SecurityMode.SimPin 118 mobileConnectionsRepository.isAnySimSecure.value = true 119 assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Sim) 120 assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Sim) 121 } 122 123 @Test isAutoConfirmFeatureEnablednull124 fun isAutoConfirmFeatureEnabled() = 125 testScope.runTest { 126 whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[0].id)).thenReturn(true) 127 whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[1].id)).thenReturn(false) 128 129 val values by collectValues(underTest.isAutoConfirmFeatureEnabled) 130 assertThat(values.first()).isFalse() 131 assertThat(values.last()).isTrue() 132 133 userRepository.setSelectedUserInfo(USER_INFOS[1]) 134 assertThat(values.last()).isFalse() 135 } 136 137 @Test isPatternVisiblenull138 fun isPatternVisible() = 139 testScope.runTest { 140 whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[0].id)).thenReturn(false) 141 whenever(lockPatternUtils.isVisiblePatternEnabled(USER_INFOS[1].id)).thenReturn(true) 142 143 val values by collectValues(underTest.isPatternVisible) 144 assertThat(values.first()).isTrue() 145 assertThat(values.last()).isFalse() 146 147 userRepository.setSelectedUserInfo(USER_INFOS[1]) 148 assertThat(values.last()).isTrue() 149 } 150 151 @Test isPinEnhancedPrivacyEnablednull152 fun isPinEnhancedPrivacyEnabled() = 153 testScope.runTest { 154 whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id)) 155 .thenReturn(false) 156 whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[1].id)) 157 .thenReturn(true) 158 159 val values by collectValues(underTest.isPinEnhancedPrivacyEnabled) 160 assertThat(values.first()).isTrue() 161 assertThat(values.last()).isFalse() 162 163 userRepository.setSelectedUserInfo(USER_INFOS[1]) 164 assertThat(values.last()).isTrue() 165 } 166 167 @Test lockoutEndTimestampnull168 fun lockoutEndTimestamp() = 169 testScope.runTest { 170 val lockoutEndMs = clock.elapsedRealtime() + 30.seconds.inWholeMilliseconds 171 whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[0].id)) 172 .thenReturn(lockoutEndMs) 173 whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[1].id)).thenReturn(0) 174 175 // Switch to a user who is not locked-out. 176 userRepository.setSelectedUserInfo(USER_INFOS[1]) 177 assertThat(underTest.lockoutEndTimestamp).isNull() 178 179 // Switch back to the locked-out user, verify the timestamp is up-to-date. 180 userRepository.setSelectedUserInfo(USER_INFOS[0]) 181 assertThat(underTest.lockoutEndTimestamp).isEqualTo(lockoutEndMs) 182 183 // After the lockout expires, null is returned. 184 clock.setElapsedRealtime(lockoutEndMs) 185 assertThat(underTest.lockoutEndTimestamp).isNull() 186 } 187 188 @Test hasLockoutOccurrednull189 fun hasLockoutOccurred() = 190 testScope.runTest { 191 val hasLockoutOccurred by collectLastValue(underTest.hasLockoutOccurred) 192 assertThat(hasLockoutOccurred).isFalse() 193 194 underTest.reportLockoutStarted(1000) 195 assertThat(hasLockoutOccurred).isTrue() 196 197 clock.setElapsedRealtime(clock.elapsedRealtime() + 60.seconds.inWholeMilliseconds) 198 199 underTest.reportAuthenticationAttempt(isSuccessful = false) 200 assertThat(hasLockoutOccurred).isTrue() 201 202 underTest.reportAuthenticationAttempt(isSuccessful = true) 203 assertThat(hasLockoutOccurred).isFalse() 204 } 205 setSecurityModeAndDispatchBroadcastnull206 private fun setSecurityModeAndDispatchBroadcast( 207 securityMode: KeyguardSecurityModel.SecurityMode, 208 ) { 209 currentSecurityMode = securityMode 210 dispatchBroadcast() 211 } 212 dispatchBroadcastnull213 private fun dispatchBroadcast() { 214 fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( 215 context, 216 Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED) 217 ) 218 } 219 220 companion object { 221 private val USER_INFOS = 222 listOf( 223 UserInfo( 224 /* id= */ 100, 225 /* name= */ "First user", 226 /* flags= */ 0, 227 ), 228 UserInfo( 229 /* id= */ 101, 230 /* name= */ "Second user", 231 /* flags= */ 0, 232 ), 233 ) 234 } 235 } 236