diff options
6 files changed, 122 insertions, 9 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 6b371d74eacc..9b47eaddffd6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.user.data.repository import android.app.admin.devicePolicyManager import android.content.pm.UserInfo +import android.internal.statusbar.fakeStatusBarService import android.os.UserHandle import android.os.UserManager import android.provider.Settings @@ -61,6 +62,7 @@ class UserRepositoryImplTest : SysuiTestCase() { private val globalSettings = kosmos.fakeGlobalSettings private val broadcastDispatcher = kosmos.broadcastDispatcher private val devicePolicyManager = kosmos.devicePolicyManager + private val statusBarService = kosmos.fakeStatusBarService @Mock private lateinit var manager: UserManager @@ -323,6 +325,8 @@ class UserRepositoryImplTest : SysuiTestCase() { tracker = tracker, broadcastDispatcher = broadcastDispatcher, devicePolicyManager = devicePolicyManager, + resources = context.resources, + statusBarService = statusBarService, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt index 26439df45ba3..f70b42638cda 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt @@ -49,35 +49,78 @@ class UserLogoutInteractorTest : SysuiTestCase() { @Before fun setUp() { userRepository.setUserInfos(USER_INFOS) - runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[1]) } + runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[2]) } + userRepository.setLogoutToSystemUserEnabled(false) + userRepository.setSecondaryUserLogoutEnabled(false) } @Test - fun logOut_doesNothing_whenAdminDisabledSecondaryLogout() { + fun logOut_doesNothing_whenBothLogoutOptionsAreDisabled() { testScope.runTest { val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) - val lastLogoutCount = userRepository.logOutSecondaryUserCallCount - userRepository.setSecondaryUserLogoutEnabled(false) + val secondaryUserLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount assertThat(isLogoutEnabled).isFalse() underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount) + .isEqualTo(secondaryUserLogoutCount) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) + } + } + + @Test + fun logOut_logsOutSecondaryUser_whenAdminEnabledSecondaryLogout() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setSecondaryUserLogoutEnabled(true) + assertThat(isLogoutEnabled).isTrue() + underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) + } + } + + @Test + fun logOut_logsOutToSystemUser_whenLogoutToSystemUserIsEnabled() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setLogoutToSystemUserEnabled(true) + assertThat(isLogoutEnabled).isTrue() + underTest.logOut() assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount + 1) } } @Test - fun logOut_logsOut_whenAdminEnabledSecondaryLogout() { + fun logOut_secondaryUserTakesPrecedence() { testScope.runTest { val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setLogoutToSystemUserEnabled(true) userRepository.setSecondaryUserLogoutEnabled(true) assertThat(isLogoutEnabled).isTrue() underTest.logOut() assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) } } companion object { private val USER_INFOS = - listOf(UserInfo(0, "System user", 0), UserInfo(10, "Regular user", 0)) + listOf( + UserInfo(0, "System user", 0), + UserInfo(10, "Regular user", 0), + UserInfo(11, "Secondary user", 0), + ) } } diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 82c8c44f1efe..0854eb46ffdd 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -1086,4 +1086,9 @@ enable the desktop specific features. --> <bool name="config_enableDesktopFeatureSet">false</bool> + + <!-- + Whether the user switching can only happen by logging out and going through the system user (login screen). + --> + <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index f20ce63467f7..e9a33e062c60 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -23,11 +23,13 @@ import android.app.admin.DevicePolicyManager import android.content.Context import android.content.IntentFilter import android.content.pm.UserInfo +import android.content.res.Resources import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.internal.statusbar.IStatusBarService import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -109,6 +111,9 @@ interface UserRepository { /** Whether logout for secondary users is enabled by admin device policy. */ val isSecondaryUserLogoutEnabled: StateFlow<Boolean> + /** Whether logout into system user is enabled. */ + val isLogoutToSystemUserEnabled: StateFlow<Boolean> + /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */ fun refreshUsers() @@ -121,6 +126,9 @@ interface UserRepository { /** Performs logout logout for secondary users. */ suspend fun logOutSecondaryUser() + /** Performs logout into the system user. */ + suspend fun logOutToSystemUser() + /** * Returns the user ID of the "main user" of the device. This user may have access to certain * features which are limited to at most one user. There will never be more than one main user @@ -143,6 +151,7 @@ class UserRepositoryImpl @Inject constructor( @Application private val appContext: Context, + @Main private val resources: Resources, private val manager: UserManager, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, @@ -151,6 +160,7 @@ constructor( private val tracker: UserTracker, private val devicePolicyManager: DevicePolicyManager, private val broadcastDispatcher: BroadcastDispatcher, + private val statusBarService: IStatusBarService, ) : UserRepository { private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> = @@ -275,12 +285,34 @@ constructor( .stateIn(applicationScope, SharingStarted.Eagerly, false) @SuppressLint("MissingPermission") + override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = + selectedUser + .flatMapLatestConflated { selectedUser -> + if (selectedUser.isEligibleForLogout()) { + flowOf( + resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen) + ) + } else { + flowOf(false) + } + } + .stateIn(applicationScope, SharingStarted.Eagerly, false) + + @SuppressLint("MissingPermission") override suspend fun logOutSecondaryUser() { if (isSecondaryUserLogoutEnabled.value) { withContext(backgroundDispatcher) { devicePolicyManager.logoutUser() } } } + override suspend fun logOutToSystemUser() { + // TODO(b/377493351) : start using proper logout API once it is available. + // Using reboot is a temporary solution. + if (isLogoutToSystemUserEnabled.value) { + withContext(backgroundDispatcher) { statusBarService.reboot(false) } + } + } + @SuppressLint("MissingPermission") override fun refreshUsers() { applicationScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt index 154f1dc3e747..f2dd25fecf08 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt @@ -23,7 +23,10 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn /** Encapsulates business logic to for the logout. */ @SysUISingleton @@ -33,11 +36,22 @@ constructor( private val userRepository: UserRepository, @Application private val applicationScope: CoroutineScope, ) { - val isLogoutEnabled: StateFlow<Boolean> = userRepository.isSecondaryUserLogoutEnabled + + val isLogoutEnabled: StateFlow<Boolean> = + combine( + userRepository.isSecondaryUserLogoutEnabled, + userRepository.isLogoutToSystemUserEnabled, + Boolean::or, + ) + .stateIn(applicationScope, SharingStarted.Eagerly, false) fun logOut() { - if (userRepository.isSecondaryUserLogoutEnabled.value) { - applicationScope.launch { userRepository.logOutSecondaryUser() } + applicationScope.launch { + if (userRepository.isSecondaryUserLogoutEnabled.value) { + userRepository.logOutSecondaryUser() + } else if (userRepository.isLogoutToSystemUserEnabled.value) { + userRepository.logOutToSystemUser() + } } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 1808a5f99f4e..85d582a27faf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -72,6 +72,10 @@ class FakeUserRepository @Inject constructor() : UserRepository { override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> = _isSecondaryUserLogoutEnabled.asStateFlow() + private val _isLogoutToSystemUserEnabled = MutableStateFlow<Boolean>(false) + override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = + _isLogoutToSystemUserEnabled.asStateFlow() + override var mainUserId: Int = MAIN_USER_ID override var lastSelectedNonGuestUserId: Int = mainUserId @@ -123,6 +127,17 @@ class FakeUserRepository @Inject constructor() : UserRepository { logOutSecondaryUserCallCount++ } + fun setLogoutToSystemUserEnabled(logoutEnabled: Boolean) { + _isLogoutToSystemUserEnabled.value = logoutEnabled + } + + var logOutToSystemUserCallCount: Int = 0 + private set + + override suspend fun logOutToSystemUser() { + logOutToSystemUserCallCount++ + } + fun setUserInfos(infos: List<UserInfo>) { _userInfos.value = infos } |