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