Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aa3e1294 authored by Denis's avatar Denis
Browse files

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
parent 7e4247a3
Loading
Loading
Loading
Loading
+78 −14
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.content.pm.UserInfo
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 @@ class UserRepositoryImplTest : SysuiTestCase() {
    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 @@ class UserRepositoryImplTest : SysuiTestCase() {
        }

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

            tracker.onProfileChanged()
            assertThat(userManagerLogoutEnabled).isFalse()
        }

    @Test
    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_regularUserLogoutEnabled() =
        testScope.runTest {
            underTest = create(testScope.backgroundScope)
            setUserSwitchingMustGoThroughLoginScreen(true)
            setUpUsers(
                count = 3,
                selectedIndex = 1,
                isFirstSystemUser = true,
                isLastGuestUser = true,
            )
            val userManagerLogoutEnabled by collectLastValue(underTest.isUserManagerLogoutEnabled)

            setUpUsers(count = 2, selectedIndex = 1)
            tracker.onProfileChanged()
            assertThat(userManagerLogoutEnabled).isFalse()
            assertThat(userManagerLogoutEnabled).isTrue()
        }

    @Test
    fun isLogoutWithManager() =
    @EnableFlags(android.multiuser.Flags.FLAG_LOGOUT_USER_API)
    fun isLogoutWithUserManagerEnabled_userManagerLogoutEnabled_guestUserLogoutEnabled() =
        testScope.runTest {
            underTest = create(testScope.backgroundScope)
            mockPolicyManagerLogoutUser(LogoutUserResult.NON_SYSTEM_CURRENT)
            setUserSwitchingMustGoThroughLoginScreen(true)
            setUpUsers(count = 2, selectedIndex = 0)
            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)

            setUpUsers(count = 2, selectedIndex = 1)
            tracker.onProfileChanged()
            assertThat(userManagerLogoutEnabled).isTrue()
        }

    private fun createUserInfo(id: Int, isGuest: Boolean): UserInfo {
    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 @@ class UserRepositoryImplTest : SysuiTestCase() {
            LogoutUserResult.NONE -> {
                whenever(devicePolicyManager.logoutUser).thenReturn(null)
            }

            LogoutUserResult.NON_SYSTEM_CURRENT -> {
                whenever(devicePolicyManager.logoutUser).thenAnswer {
                    if (tracker.userHandle != UserHandle.SYSTEM) {
+27 −29
Original line number Diff line number Diff line
@@ -266,32 +266,25 @@ constructor(

    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()
            )
            .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 @@ constructor(
    @SuppressLint("MissingPermission")
    override val isUserManagerLogoutEnabled: StateFlow<Boolean> =
        selectedUser
            .flatMapLatestConflated { selectedUser ->
                if (selectedUser.isEligibleForLogout()) {
                    flowOf(
                        resources.getBoolean(
            .map { selectedUser ->
                when {
                    !resources.getBoolean(
                        com.android.internal.R.bool.config_userSwitchingMustGoThroughLoginScreen
                        )
                    )
                } else {
                    flowOf(false)
                    ) -> 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 @@ constructor(
    }

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