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) {