1 /*
2  * Copyright (C) 2022 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 
18 package com.android.systemui.user.ui.viewmodel
19 
20 import android.app.ActivityManager
21 import android.app.admin.DevicePolicyManager
22 import android.content.pm.UserInfo
23 import android.os.UserManager
24 import androidx.test.filters.SmallTest
25 import com.android.internal.logging.UiEventLogger
26 import com.android.keyguard.KeyguardUpdateMonitor
27 import com.android.systemui.GuestResetOrExitSessionReceiver
28 import com.android.systemui.GuestResumeSessionReceiver
29 import com.android.systemui.SysuiTestCase
30 import com.android.systemui.common.shared.model.Text
31 import com.android.systemui.flags.FakeFeatureFlags
32 import com.android.systemui.flags.Flags
33 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
34 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
35 import com.android.systemui.plugins.ActivityStarter
36 import com.android.systemui.statusbar.policy.DeviceProvisionedController
37 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
38 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
39 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
40 import com.android.systemui.user.data.repository.FakeUserRepository
41 import com.android.systemui.user.domain.interactor.GuestUserInteractor
42 import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
43 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
44 import com.android.systemui.user.domain.interactor.UserInteractor
45 import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
46 import com.android.systemui.user.shared.model.UserActionModel
47 import com.android.systemui.util.mockito.any
48 import com.android.systemui.util.mockito.whenever
49 import com.google.common.truth.Truth.assertThat
50 import kotlinx.coroutines.ExperimentalCoroutinesApi
51 import kotlinx.coroutines.flow.toList
52 import kotlinx.coroutines.launch
53 import kotlinx.coroutines.runBlocking
54 import kotlinx.coroutines.test.TestDispatcher
55 import kotlinx.coroutines.test.TestScope
56 import kotlinx.coroutines.test.UnconfinedTestDispatcher
57 import kotlinx.coroutines.test.runCurrent
58 import kotlinx.coroutines.test.runTest
59 import org.junit.Before
60 import org.junit.Test
61 import org.junit.runner.RunWith
62 import org.junit.runners.JUnit4
63 import org.mockito.Mock
64 import org.mockito.MockitoAnnotations
65 
66 @OptIn(ExperimentalCoroutinesApi::class)
67 @SmallTest
68 @RunWith(JUnit4::class)
69 class UserSwitcherViewModelTest : SysuiTestCase() {
70 
71     @Mock private lateinit var activityStarter: ActivityStarter
72     @Mock private lateinit var activityManager: ActivityManager
73     @Mock private lateinit var manager: UserManager
74     @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
75     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
76     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
77     @Mock private lateinit var uiEventLogger: UiEventLogger
78     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
79     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
80     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
81 
82     private lateinit var underTest: UserSwitcherViewModel
83 
84     private lateinit var userRepository: FakeUserRepository
85     private lateinit var keyguardRepository: FakeKeyguardRepository
86 
87     private lateinit var testDispatcher: TestDispatcher
88     private lateinit var testScope: TestScope
89 
90     @Before
91     fun setUp() {
92         MockitoAnnotations.initMocks(this)
93         whenever(manager.canAddMoreUsers(any())).thenReturn(true)
94         whenever(manager.getUserSwitchability(any()))
95             .thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
96         overrideResource(
97             com.android.internal.R.string.config_supervisedUserCreationPackage,
98             SUPERVISED_USER_CREATION_PACKAGE,
99         )
100 
101         testDispatcher = UnconfinedTestDispatcher()
102         testScope = TestScope(testDispatcher)
103         userRepository = FakeUserRepository()
104         runBlocking {
105             val userInfos =
106                 listOf(
107                     UserInfo(
108                         /* id= */ 0,
109                         /* name= */ "zero",
110                         /* iconPath= */ "",
111                         /* flags= */ UserInfo.FLAG_PRIMARY or
112                             UserInfo.FLAG_ADMIN or
113                             UserInfo.FLAG_FULL,
114                         UserManager.USER_TYPE_FULL_SYSTEM,
115                     ),
116                 )
117             userRepository.setUserInfos(userInfos)
118             userRepository.setSelectedUserInfo(userInfos[0])
119             userRepository.setSettings(
120                 UserSwitcherSettingsModel(
121                     isUserSwitcherEnabled = true,
122                 )
123             )
124         }
125 
126         val refreshUsersScheduler =
127             RefreshUsersScheduler(
128                 applicationScope = testScope.backgroundScope,
129                 mainDispatcher = testDispatcher,
130                 repository = userRepository,
131             )
132         val guestUserInteractor =
133             GuestUserInteractor(
134                 applicationContext = context,
135                 applicationScope = testScope.backgroundScope,
136                 mainDispatcher = testDispatcher,
137                 backgroundDispatcher = testDispatcher,
138                 manager = manager,
139                 repository = userRepository,
140                 deviceProvisionedController = deviceProvisionedController,
141                 devicePolicyManager = devicePolicyManager,
142                 refreshUsersScheduler = refreshUsersScheduler,
143                 uiEventLogger = uiEventLogger,
144                 resumeSessionReceiver = resumeSessionReceiver,
145                 resetOrExitSessionReceiver = resetOrExitSessionReceiver,
146             )
147 
148         val featureFlags =
149             FakeFeatureFlags().apply {
150                 set(Flags.FULL_SCREEN_USER_SWITCHER, false)
151                 set(Flags.FACE_AUTH_REFACTOR, true)
152             }
153         val reply = KeyguardInteractorFactory.create(featureFlags = featureFlags)
154         keyguardRepository = reply.repository
155 
156         underTest =
157             UserSwitcherViewModel(
158                 userInteractor =
159                     UserInteractor(
160                         applicationContext = context,
161                         repository = userRepository,
162                         activityStarter = activityStarter,
163                         keyguardInteractor = reply.keyguardInteractor,
164                         featureFlags = featureFlags,
165                         manager = manager,
166                         headlessSystemUserMode = headlessSystemUserMode,
167                         applicationScope = testScope.backgroundScope,
168                         telephonyInteractor =
169                             TelephonyInteractor(
170                                 repository = FakeTelephonyRepository(),
171                             ),
172                         broadcastDispatcher = fakeBroadcastDispatcher,
173                         keyguardUpdateMonitor = keyguardUpdateMonitor,
174                         backgroundDispatcher = testDispatcher,
175                         activityManager = activityManager,
176                         refreshUsersScheduler = refreshUsersScheduler,
177                         guestUserInteractor = guestUserInteractor,
178                         uiEventLogger = uiEventLogger,
179                     ),
180                 guestUserInteractor = guestUserInteractor,
181             )
182     }
183 
184     @Test
185     fun users() =
186         testScope.runTest {
187             val userInfos =
188                 listOf(
189                     UserInfo(
190                         /* id= */ 0,
191                         /* name= */ "zero",
192                         /* iconPath= */ "",
193                         /* flags= */ UserInfo.FLAG_PRIMARY or
194                             UserInfo.FLAG_ADMIN or
195                             UserInfo.FLAG_FULL,
196                         UserManager.USER_TYPE_FULL_SYSTEM,
197                     ),
198                     UserInfo(
199                         /* id= */ 1,
200                         /* name= */ "one",
201                         /* iconPath= */ "",
202                         /* flags= */ UserInfo.FLAG_FULL,
203                         UserManager.USER_TYPE_FULL_SYSTEM,
204                     ),
205                     UserInfo(
206                         /* id= */ 2,
207                         /* name= */ "two",
208                         /* iconPath= */ "",
209                         /* flags= */ UserInfo.FLAG_FULL,
210                         UserManager.USER_TYPE_FULL_SYSTEM,
211                     ),
212                 )
213             userRepository.setUserInfos(userInfos)
214             userRepository.setSelectedUserInfo(userInfos[0])
215 
216             val userViewModels = mutableListOf<List<UserViewModel>>()
217             val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
218 
219             assertThat(userViewModels.last()).hasSize(3)
220             assertUserViewModel(
221                 viewModel = userViewModels.last()[0],
222                 viewKey = 0,
223                 name = Text.Loaded("zero"),
224                 isSelectionMarkerVisible = true,
225             )
226             assertUserViewModel(
227                 viewModel = userViewModels.last()[1],
228                 viewKey = 1,
229                 name = Text.Loaded("one"),
230                 isSelectionMarkerVisible = false,
231             )
232             assertUserViewModel(
233                 viewModel = userViewModels.last()[2],
234                 viewKey = 2,
235                 name = Text.Loaded("two"),
236                 isSelectionMarkerVisible = false,
237             )
238             job.cancel()
239         }
240 
241     @Test
242     fun maximumUserColumns_fewUsers() =
243         testScope.runTest {
244             setUsers(count = 2)
245             val values = mutableListOf<Int>()
246             val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
247 
248             assertThat(values.last()).isEqualTo(4)
249 
250             job.cancel()
251         }
252 
253     @Test
254     fun maximumUserColumns_manyUsers() =
255         testScope.runTest {
256             setUsers(count = 5)
257             val values = mutableListOf<Int>()
258             val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) }
259 
260             assertThat(values.last()).isEqualTo(3)
261             job.cancel()
262         }
263 
264     @Test
265     fun isOpenMenuButtonVisible_hasActions_true() =
266         testScope.runTest {
267             setUsers(2)
268 
269             val isVisible = mutableListOf<Boolean>()
270             val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
271 
272             assertThat(isVisible.last()).isTrue()
273             job.cancel()
274         }
275 
276     @Test
277     fun isOpenMenuButtonVisible_noActions_false() =
278         testScope.runTest {
279             val userInfos = setUsers(2)
280             userRepository.setSelectedUserInfo(userInfos[1])
281             keyguardRepository.setKeyguardShowing(true)
282             whenever(manager.canAddMoreUsers(any())).thenReturn(false)
283 
284             val isVisible = mutableListOf<Boolean>()
285             val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
286 
287             assertThat(isVisible.last()).isFalse()
288             job.cancel()
289         }
290 
291     @Test
292     fun menu() =
293         testScope.runTest {
294             val isMenuVisible = mutableListOf<Boolean>()
295             val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) }
296             assertThat(isMenuVisible.last()).isFalse()
297 
298             underTest.onOpenMenuButtonClicked()
299             assertThat(isMenuVisible.last()).isTrue()
300 
301             underTest.onMenuClosed()
302             assertThat(isMenuVisible.last()).isFalse()
303 
304             job.cancel()
305         }
306 
307     @Test
308     fun menuActions() =
309         testScope.runTest {
310             setUsers(2)
311             val actions = mutableListOf<List<UserActionViewModel>>()
312             val job = launch(testDispatcher) { underTest.menu.toList(actions) }
313 
314             assertThat(actions.last().map { it.viewKey })
315                 .isEqualTo(
316                     listOf(
317                         UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(),
318                         UserActionModel.ADD_USER.ordinal.toLong(),
319                         UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(),
320                         UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(),
321                     )
322                 )
323 
324             job.cancel()
325         }
326 
327     @Test
328     fun isFinishRequested_finishesWhenCancelButtonIsClicked() =
329         testScope.runTest {
330             setUsers(count = 2)
331             val isFinishRequested = mutableListOf<Boolean>()
332             val job =
333                 launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
334             assertThat(isFinishRequested.last()).isFalse()
335 
336             underTest.onCancelButtonClicked()
337 
338             assertThat(isFinishRequested.last()).isTrue()
339 
340             underTest.onFinished()
341 
342             assertThat(isFinishRequested.last()).isFalse()
343 
344             job.cancel()
345         }
346 
347     @Test
348     fun guestSelected_nameIsExitGuest() =
349         testScope.runTest {
350             val userInfos =
351                 listOf(
352                     UserInfo(
353                         /* id= */ 0,
354                         /* name= */ "zero",
355                         /* iconPath= */ "",
356                         /* flags= */ UserInfo.FLAG_PRIMARY or
357                             UserInfo.FLAG_ADMIN or
358                             UserInfo.FLAG_FULL,
359                         UserManager.USER_TYPE_FULL_SYSTEM,
360                     ),
361                     UserInfo(
362                         /* id= */ 1,
363                         /* name= */ "one",
364                         /* iconPath= */ "",
365                         /* flags= */ UserInfo.FLAG_FULL,
366                         UserManager.USER_TYPE_FULL_GUEST,
367                     ),
368                 )
369 
370             userRepository.setUserInfos(userInfos)
371             userRepository.setSelectedUserInfo(userInfos[1])
372 
373             val userViewModels = mutableListOf<List<UserViewModel>>()
374             val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
375 
376             assertThat(userViewModels.last()).hasSize(2)
377             assertUserViewModel(
378                 viewModel = userViewModels.last()[0],
379                 viewKey = 0,
380                 name = Text.Loaded("zero"),
381                 isSelectionMarkerVisible = false,
382             )
383             assertUserViewModel(
384                 viewModel = userViewModels.last()[1],
385                 viewKey = 1,
386                 name =
387                     Text.Resource(
388                         com.android.settingslib.R.string.guest_exit_quick_settings_button
389                     ),
390                 isSelectionMarkerVisible = true,
391             )
392             job.cancel()
393         }
394 
395     @Test
396     fun guestNotSelected_nameIsGuest() =
397         testScope.runTest {
398             val userInfos =
399                 listOf(
400                     UserInfo(
401                         /* id= */ 0,
402                         /* name= */ "zero",
403                         /* iconPath= */ "",
404                         /* flags= */ UserInfo.FLAG_PRIMARY or
405                             UserInfo.FLAG_ADMIN or
406                             UserInfo.FLAG_FULL,
407                         UserManager.USER_TYPE_FULL_SYSTEM,
408                     ),
409                     UserInfo(
410                         /* id= */ 1,
411                         /* name= */ "one",
412                         /* iconPath= */ "",
413                         /* flags= */ UserInfo.FLAG_FULL,
414                         UserManager.USER_TYPE_FULL_GUEST,
415                     ),
416                 )
417 
418             userRepository.setUserInfos(userInfos)
419             userRepository.setSelectedUserInfo(userInfos[0])
420             runCurrent()
421 
422             val userViewModels = mutableListOf<List<UserViewModel>>()
423             val job = launch(testDispatcher) { underTest.users.toList(userViewModels) }
424 
425             assertThat(userViewModels.last()).hasSize(2)
426             assertUserViewModel(
427                 viewModel = userViewModels.last()[0],
428                 viewKey = 0,
429                 name = Text.Loaded("zero"),
430                 isSelectionMarkerVisible = true,
431             )
432             assertUserViewModel(
433                 viewModel = userViewModels.last()[1],
434                 viewKey = 1,
435                 name = Text.Loaded("one"),
436                 isSelectionMarkerVisible = false,
437             )
438             job.cancel()
439         }
440 
441     private suspend fun setUsers(count: Int): List<UserInfo> {
442         val userInfos =
443             (0 until count).map { index ->
444                 UserInfo(
445                     /* id= */ index,
446                     /* name= */ "$index",
447                     /* iconPath= */ "",
448                     /* flags= */ if (index == 0) {
449                         // This is the primary user.
450                         UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL
451                     } else {
452                         // This isn't the primary user.
453                         UserInfo.FLAG_FULL
454                     },
455                     UserManager.USER_TYPE_FULL_SYSTEM,
456                 )
457             }
458         userRepository.setUserInfos(userInfos)
459 
460         if (userInfos.isNotEmpty()) {
461             userRepository.setSelectedUserInfo(userInfos[0])
462         }
463         return userInfos
464     }
465 
466     private fun assertUserViewModel(
467         viewModel: UserViewModel?,
468         viewKey: Int,
469         name: Text,
470         isSelectionMarkerVisible: Boolean,
471     ) {
472         checkNotNull(viewModel)
473         assertThat(viewModel.viewKey).isEqualTo(viewKey)
474         assertThat(viewModel.name).isEqualTo(name)
475         assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible)
476         assertThat(viewModel.alpha)
477             .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA)
478         assertThat(viewModel.onClicked).isNotNull()
479     }
480 
481     companion object {
482         private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
483     }
484 }
485