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