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