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