| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package com.android.systemui.user.data.repository |
| |
| import android.app.IActivityManager |
| import android.app.UserSwitchObserver |
| import android.content.pm.UserInfo |
| import android.os.IRemoteCallback |
| import android.os.UserHandle |
| import android.os.UserManager |
| import android.provider.Settings |
| import androidx.test.filters.SmallTest |
| import com.android.systemui.SysuiTestCase |
| import com.android.systemui.flags.FakeFeatureFlags |
| import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR |
| import com.android.systemui.settings.FakeUserTracker |
| import com.android.systemui.user.data.model.UserSwitcherSettingsModel |
| import com.android.systemui.util.settings.FakeSettings |
| import com.google.common.truth.Truth.assertThat |
| import kotlinx.coroutines.CoroutineScope |
| import kotlinx.coroutines.Dispatchers |
| import kotlinx.coroutines.Job |
| import kotlinx.coroutines.cancel |
| import kotlinx.coroutines.flow.launchIn |
| import kotlinx.coroutines.flow.onEach |
| import kotlinx.coroutines.runBlocking |
| import kotlinx.coroutines.test.TestCoroutineScope |
| import org.junit.Before |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.junit.runners.JUnit4 |
| import org.mockito.ArgumentCaptor |
| import org.mockito.Captor |
| import org.mockito.Mock |
| import org.mockito.Mockito.any |
| import org.mockito.Mockito.anyString |
| import org.mockito.Mockito.mock |
| import org.mockito.Mockito.times |
| import org.mockito.Mockito.verify |
| import org.mockito.Mockito.`when` as whenever |
| import org.mockito.MockitoAnnotations |
| |
| @SmallTest |
| @RunWith(JUnit4::class) |
| class UserRepositoryImplTest : SysuiTestCase() { |
| |
| @Mock private lateinit var manager: UserManager |
| @Mock private lateinit var activityManager: IActivityManager |
| @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver> |
| |
| private lateinit var underTest: UserRepositoryImpl |
| |
| private lateinit var globalSettings: FakeSettings |
| private lateinit var tracker: FakeUserTracker |
| |
| @Before |
| fun setUp() { |
| MockitoAnnotations.initMocks(this) |
| |
| globalSettings = FakeSettings() |
| tracker = FakeUserTracker() |
| } |
| |
| @Test |
| fun userSwitcherSettings() = runSelfCancelingTest { |
| setUpGlobalSettings( |
| isSimpleUserSwitcher = true, |
| isAddUsersFromLockscreen = true, |
| isUserSwitcherEnabled = true, |
| ) |
| underTest = create(this) |
| |
| var value: UserSwitcherSettingsModel? = null |
| underTest.userSwitcherSettings.onEach { value = it }.launchIn(this) |
| |
| assertUserSwitcherSettings( |
| model = value, |
| expectedSimpleUserSwitcher = true, |
| expectedAddUsersFromLockscreen = true, |
| expectedUserSwitcherEnabled = true, |
| ) |
| |
| setUpGlobalSettings( |
| isSimpleUserSwitcher = false, |
| isAddUsersFromLockscreen = true, |
| isUserSwitcherEnabled = true, |
| ) |
| assertUserSwitcherSettings( |
| model = value, |
| expectedSimpleUserSwitcher = false, |
| expectedAddUsersFromLockscreen = true, |
| expectedUserSwitcherEnabled = true, |
| ) |
| } |
| |
| @Test |
| fun userSwitcherSettings_isUserSwitcherEnabled_notInitialized() = runSelfCancelingTest { |
| underTest = create(this) |
| |
| var value: UserSwitcherSettingsModel? = null |
| underTest.userSwitcherSettings.onEach { value = it }.launchIn(this) |
| |
| assertUserSwitcherSettings( |
| model = value, |
| expectedSimpleUserSwitcher = false, |
| expectedAddUsersFromLockscreen = false, |
| expectedUserSwitcherEnabled = |
| context.resources.getBoolean( |
| com.android.internal.R.bool.config_showUserSwitcherByDefault |
| ), |
| ) |
| } |
| |
| @Test |
| fun refreshUsers() = runSelfCancelingTest { |
| underTest = create(this) |
| val initialExpectedValue = |
| setUpUsers( |
| count = 3, |
| selectedIndex = 0, |
| ) |
| var userInfos: List<UserInfo>? = null |
| var selectedUserInfo: UserInfo? = null |
| underTest.userInfos.onEach { userInfos = it }.launchIn(this) |
| underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this) |
| |
| underTest.refreshUsers() |
| assertThat(userInfos).isEqualTo(initialExpectedValue) |
| assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0]) |
| assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id) |
| |
| val secondExpectedValue = |
| setUpUsers( |
| count = 4, |
| selectedIndex = 1, |
| ) |
| underTest.refreshUsers() |
| assertThat(userInfos).isEqualTo(secondExpectedValue) |
| assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1]) |
| assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id) |
| |
| val selectedNonGuestUserId = selectedUserInfo?.id |
| val thirdExpectedValue = |
| setUpUsers( |
| count = 2, |
| isLastGuestUser = true, |
| selectedIndex = 1, |
| ) |
| underTest.refreshUsers() |
| assertThat(userInfos).isEqualTo(thirdExpectedValue) |
| assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1]) |
| assertThat(selectedUserInfo?.isGuest).isTrue() |
| assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId) |
| } |
| |
| @Test |
| fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest { |
| underTest = create(this) |
| val unsortedUsers = |
| setUpUsers( |
| count = 3, |
| selectedIndex = 0, |
| isLastGuestUser = true, |
| ) |
| unsortedUsers[0].creationTime = 999 |
| unsortedUsers[1].creationTime = 900 |
| unsortedUsers[2].creationTime = 950 |
| val expectedUsers = |
| listOf( |
| unsortedUsers[1], |
| unsortedUsers[0], |
| unsortedUsers[2], // last because this is the guest |
| ) |
| var userInfos: List<UserInfo>? = null |
| underTest.userInfos.onEach { userInfos = it }.launchIn(this) |
| |
| underTest.refreshUsers() |
| assertThat(userInfos).isEqualTo(expectedUsers) |
| } |
| |
| private fun setUpUsers( |
| count: Int, |
| isLastGuestUser: Boolean = false, |
| selectedIndex: Int = 0, |
| ): List<UserInfo> { |
| val userInfos = |
| (0 until count).map { index -> |
| createUserInfo( |
| index, |
| isGuest = isLastGuestUser && index == count - 1, |
| ) |
| } |
| whenever(manager.aliveUsers).thenReturn(userInfos) |
| tracker.set(userInfos, selectedIndex) |
| return userInfos |
| } |
| @Test |
| fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest { |
| underTest = create(this) |
| var selectedUserInfo: UserInfo? = null |
| underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this) |
| setUpUsers( |
| count = 2, |
| selectedIndex = 0, |
| ) |
| tracker.onProfileChanged() |
| assertThat(selectedUserInfo?.id).isEqualTo(0) |
| setUpUsers( |
| count = 2, |
| selectedIndex = 1, |
| ) |
| tracker.onProfileChanged() |
| assertThat(selectedUserInfo?.id).isEqualTo(1) |
| } |
| |
| @Test |
| fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest { |
| underTest = create(this) |
| |
| underTest.userSwitchingInProgress.launchIn(this) |
| underTest.userSwitchingInProgress.launchIn(this) |
| underTest.userSwitchingInProgress.launchIn(this) |
| |
| verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString()) |
| } |
| |
| @Test |
| fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest { |
| underTest = create(this) |
| verify(activityManager) |
| .registerUserSwitchObserver(userSwitchObserver.capture(), anyString()) |
| |
| userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java)) |
| |
| var mostRecentSwitchingValue = false |
| underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this) |
| |
| assertThat(mostRecentSwitchingValue).isTrue() |
| |
| userSwitchObserver.value.onUserSwitchComplete(0) |
| assertThat(mostRecentSwitchingValue).isFalse() |
| } |
| |
| private fun createUserInfo( |
| id: Int, |
| isGuest: Boolean, |
| ): UserInfo { |
| val flags = 0 |
| return UserInfo( |
| id, |
| "user_$id", |
| /* iconPath= */ "", |
| flags, |
| if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags), |
| ) |
| } |
| |
| private fun setUpGlobalSettings( |
| isSimpleUserSwitcher: Boolean = false, |
| isAddUsersFromLockscreen: Boolean = false, |
| isUserSwitcherEnabled: Boolean = true, |
| ) { |
| context.orCreateTestableResources.addOverride( |
| com.android.internal.R.bool.config_expandLockScreenUserSwitcher, |
| true, |
| ) |
| globalSettings.putIntForUser( |
| UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER, |
| if (isSimpleUserSwitcher) 1 else 0, |
| UserHandle.USER_SYSTEM, |
| ) |
| globalSettings.putIntForUser( |
| Settings.Global.ADD_USERS_WHEN_LOCKED, |
| if (isAddUsersFromLockscreen) 1 else 0, |
| UserHandle.USER_SYSTEM, |
| ) |
| globalSettings.putIntForUser( |
| Settings.Global.USER_SWITCHER_ENABLED, |
| if (isUserSwitcherEnabled) 1 else 0, |
| UserHandle.USER_SYSTEM, |
| ) |
| } |
| |
| private fun assertUserSwitcherSettings( |
| model: UserSwitcherSettingsModel?, |
| expectedSimpleUserSwitcher: Boolean, |
| expectedAddUsersFromLockscreen: Boolean, |
| expectedUserSwitcherEnabled: Boolean, |
| ) { |
| checkNotNull(model) |
| assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher) |
| assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen) |
| assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled) |
| } |
| |
| /** |
| * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which |
| * is then automatically canceled and cleaned-up. |
| */ |
| private fun runSelfCancelingTest( |
| block: suspend CoroutineScope.() -> Unit, |
| ) = |
| runBlocking(Dispatchers.Main.immediate) { |
| val scope = CoroutineScope(coroutineContext + Job()) |
| block(scope) |
| scope.cancel() |
| } |
| |
| private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl { |
| val featureFlags = FakeFeatureFlags() |
| featureFlags.set(FACE_AUTH_REFACTOR, true) |
| return UserRepositoryImpl( |
| appContext = context, |
| manager = manager, |
| applicationScope = scope, |
| mainDispatcher = IMMEDIATE, |
| backgroundDispatcher = IMMEDIATE, |
| globalSettings = globalSettings, |
| tracker = tracker, |
| activityManager = activityManager, |
| featureFlags = featureFlags, |
| ) |
| } |
| |
| companion object { |
| @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate |
| } |
| } |