Loading packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -323,6 +325,8 @@ class UserRepositoryImplTest : SysuiTestCase() { tracker = tracker, broadcastDispatcher = broadcastDispatcher, devicePolicyManager = devicePolicyManager, resources = context.resources, statusBarService = statusBarService, ) } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt +49 −6 Original line number Diff line number Diff line Loading @@ -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), ) } } packages/SystemUI/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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> packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +32 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading @@ -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 Loading @@ -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, Loading @@ -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> = Loading Loading @@ -274,6 +284,20 @@ 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) { Loading @@ -281,6 +305,14 @@ constructor( } } 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 { Loading packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt +17 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() { applicationScope.launch { if (userRepository.isSecondaryUserLogoutEnabled.value) { applicationScope.launch { userRepository.logOutSecondaryUser() } userRepository.logOutSecondaryUser() } else if (userRepository.isLogoutToSystemUserEnabled.value) { userRepository.logOutToSystemUser() } } } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +4 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -323,6 +325,8 @@ class UserRepositoryImplTest : SysuiTestCase() { tracker = tracker, broadcastDispatcher = broadcastDispatcher, devicePolicyManager = devicePolicyManager, resources = context.resources, statusBarService = statusBarService, ) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt +49 −6 Original line number Diff line number Diff line Loading @@ -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), ) } }
packages/SystemUI/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -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>
packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +32 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading @@ -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 Loading @@ -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, Loading @@ -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> = Loading Loading @@ -274,6 +284,20 @@ 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) { Loading @@ -281,6 +305,14 @@ constructor( } } 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 { Loading
packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt +17 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() { applicationScope.launch { if (userRepository.isSecondaryUserLogoutEnabled.value) { applicationScope.launch { userRepository.logOutSecondaryUser() } userRepository.logOutSecondaryUser() } else if (userRepository.isLogoutToSystemUserEnabled.value) { userRepository.logOutToSystemUser() } } } }