Use separate "logout eligibility" logic for managed and desktop logouts
Ability to logout residing in DevicePolicyManager relies on feature
being enabled by admin and having a remembered user system can switch to
upon logout.
Latter one is not relevant for logout logic implemented by UserManager,
that provides separate UserManager.getUserLogoutability check.
Also reordered a bit a logic inside Flows to have quick synchronous
checks run before diving into *Manager calls.
Test: atest UserRepositoryImplTest
Fixes: 404183874
Flag: EXEMPT bugfix
Change-Id: I7860df352613b4e723b9fe40d7846d3570d2496b
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 260e332..57c9e6b 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
@@ -26,6 +26,8 @@
import android.internal.statusbar.fakeStatusBarService
import android.os.UserHandle
import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -234,13 +236,26 @@
private fun setUpUsers(
count: Int,
isLastGuestUser: Boolean = false,
+ isFirstSystemUser: Boolean = false,
selectedIndex: Int = 0,
): List<UserInfo> {
val userInfos =
(0 until count).map { index ->
- createUserInfo(index, isGuest = isLastGuestUser && index == count - 1)
+ createUserInfo(
+ index,
+ isSystem = isFirstSystemUser && index == 0,
+ isGuest = isLastGuestUser && index == count - 1,
+ )
}
whenever(manager.aliveUsers).thenReturn(userInfos)
+ whenever(manager.getUserLogoutability(userInfos[selectedIndex].id))
+ .thenReturn(
+ if (isFirstSystemUser && selectedIndex == 0) {
+ UserManager.LOGOUTABILITY_STATUS_CANNOT_LOGOUT_SYSTEM_USER
+ } else {
+ UserManager.LOGOUTABILITY_STATUS_OK
+ }
+ )
tracker.set(userInfos, selectedIndex)
return userInfos
}
@@ -385,49 +400,97 @@
}
@Test
- fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_NullLogoutUser_Manager_alwaysFalse() =
+ @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_systemUserLogoutDisabled() =
testScope.runTest {
underTest = create(testScope.backgroundScope)
- mockPolicyManagerLogoutUser(LogoutUserResult.NONE)
setUserSwitchingMustGoThroughLoginScreen(true)
- setUpUsers(count = 2, selectedIndex = 0)
- tracker.onProfileChanged()
-
+ setUpUsers(
+ count = 3,
+ selectedIndex = 0,
+ isFirstSystemUser = true,
+ isLastGuestUser = true,
+ )
val userManagerLogoutEnabled by collectLastValue(underTest.isUserManagerLogoutEnabled)
- assertThat(userManagerLogoutEnabled).isFalse()
-
- setUpUsers(count = 2, selectedIndex = 1)
tracker.onProfileChanged()
assertThat(userManagerLogoutEnabled).isFalse()
}
@Test
- fun isLogoutWithManager() =
+ @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_regularUserLogoutEnabled() =
testScope.runTest {
underTest = create(testScope.backgroundScope)
- mockPolicyManagerLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
setUserSwitchingMustGoThroughLoginScreen(true)
- setUpUsers(count = 2, selectedIndex = 0)
- tracker.onProfileChanged()
-
+ setUpUsers(
+ count = 3,
+ selectedIndex = 1,
+ isFirstSystemUser = true,
+ isLastGuestUser = true,
+ )
val userManagerLogoutEnabled by collectLastValue(underTest.isUserManagerLogoutEnabled)
- assertThat(userManagerLogoutEnabled).isFalse()
-
- setUpUsers(count = 2, selectedIndex = 1)
tracker.onProfileChanged()
assertThat(userManagerLogoutEnabled).isTrue()
}
- private fun createUserInfo(id: Int, isGuest: Boolean): UserInfo {
+ @Test
+ @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_guestUserLogoutEnabled() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ setUserSwitchingMustGoThroughLoginScreen(true)
+ setUpUsers(
+ count = 3,
+ selectedIndex = 2,
+ isFirstSystemUser = true,
+ isLastGuestUser = true,
+ )
+ val userManagerLogoutEnabled by collectLastValue(underTest.isUserManagerLogoutEnabled)
+
+ tracker.onProfileChanged()
+ assertThat(userManagerLogoutEnabled).isTrue()
+ }
+
+ @Test
+ @DisableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_noLogoutApi_systemUserLogoutDisabled() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ setUserSwitchingMustGoThroughLoginScreen(true)
+ setUpUsers(count = 2, selectedIndex = 0, isFirstSystemUser = true)
+ val userManagerLogoutEnabled by collectLastValue(underTest.isUserManagerLogoutEnabled)
+
+ tracker.onProfileChanged()
+ assertThat(userManagerLogoutEnabled).isFalse()
+ }
+
+ @Test
+ @DisableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
+ fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_noLogoutApi_regularUserLogoutEnabled() =
+ testScope.runTest {
+ underTest = create(testScope.backgroundScope)
+ setUserSwitchingMustGoThroughLoginScreen(true)
+ setUpUsers(count = 2, selectedIndex = 1, isFirstSystemUser = true)
+ val userManagerLogoutEnabled by collectLastValue(underTest.isUserManagerLogoutEnabled)
+
+ tracker.onProfileChanged()
+ assertThat(userManagerLogoutEnabled).isTrue()
+ }
+
+ private fun createUserInfo(id: Int, isSystem: Boolean, isGuest: Boolean): UserInfo {
val flags = 0
return UserInfo(
id,
"user_$id",
/* iconPath= */ "",
flags,
- if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags),
+ when {
+ isSystem -> UserManager.USER_TYPE_FULL_SYSTEM
+ isGuest -> UserManager.USER_TYPE_FULL_GUEST
+ else -> UserInfo.getDefaultUserType(flags)
+ },
)
}
@@ -486,6 +549,7 @@
LogoutUserResult.NONE -> {
whenever(devicePolicyManager.logoutUser).thenReturn(null)
}
+
LogoutUserResult.NON_SYSTEM_CURRENT -> {
whenever(devicePolicyManager.logoutUser).thenAnswer {
if (tracker.userHandle != UserHandle.SYSTEM) {
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 73d546d..adf5cfa 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
@@ -266,32 +266,25 @@
override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
- /** Whether the secondary user logout is enabled by the admin device policy. */
- private val isPolicyManagerLogoutSupported: Flow<Boolean> =
+ /** Cold flow that emits upon ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcast. */
+ private val devicePolicyManagerStateChangeEvents: Flow<Unit> =
broadcastDispatcher
.broadcastFlow(
- filter =
- IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
- ) { intent, _ ->
- if (
- DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED == intent.action
- ) {
- Unit
- } else {
- null
- }
- }
- .filterNotNull()
+ IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+ )
.onStart { emit(Unit) }
- .map { _ -> devicePolicyManager.isLogoutEnabled() }
- .flowOn(backgroundDispatcher)
@SuppressLint("MissingPermission")
override val isPolicyManagerLogoutEnabled: StateFlow<Boolean> =
selectedUser
.flatMapLatestConflated { selectedUser ->
- if (selectedUser.isEligibleForLogout()) {
- isPolicyManagerLogoutSupported
+ if (selectedUser.selectionStatus == SelectionStatus.SELECTION_COMPLETE) {
+ devicePolicyManagerStateChangeEvents
+ .map { _ ->
+ devicePolicyManager.isLogoutEnabled() &&
+ devicePolicyManager.logoutUser != null
+ }
+ .flowOn(backgroundDispatcher)
} else {
flowOf(false)
}
@@ -313,15 +306,22 @@
@SuppressLint("MissingPermission")
override val isUserManagerLogoutEnabled: StateFlow<Boolean> =
selectedUser
- .flatMapLatestConflated { selectedUser ->
- if (selectedUser.isEligibleForLogout()) {
- flowOf(
- resources.getBoolean(
- com.android.internal.R.bool.config_userSwitchingMustGoThroughLoginScreen
- )
- )
- } else {
- flowOf(false)
+ .map { selectedUser ->
+ when {
+ !resources.getBoolean(
+ com.android.internal.R.bool.config_userSwitchingMustGoThroughLoginScreen
+ ) -> false
+
+ selectedUser.selectionStatus != SelectionStatus.SELECTION_COMPLETE -> false
+
+ !android.multiuser.Flags.logoutUserApi() ->
+ selectedUser.userInfo.id != UserHandle.USER_SYSTEM
+
+ else ->
+ withContext(backgroundDispatcher) {
+ manager.getUserLogoutability(selectedUser.userInfo.id) ==
+ UserManager.LOGOUTABILITY_STATUS_OK
+ }
}
}
.stateIn(applicationScope, SharingStarted.Eagerly, false)
@@ -334,8 +334,6 @@
}
override suspend fun logOutWithUserManager() {
- // TODO(b/377493351) : start using proper logout API once it is available.
- // Using reboot is a temporary solution.
if (isUserManagerLogoutEnabled.value) {
if (android.multiuser.Flags.logoutUserApi()) {
withContext(backgroundDispatcher) {