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

Commit 9e36d62b authored by Denis Kuznetsov's avatar Denis Kuznetsov Committed by Android (Google) Code Review
Browse files

Merge "Add logout to System user logic" into main

parents 73cdce53 3404a179
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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,
        )
    }

+49 −6
Original line number Diff line number Diff line
@@ -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),
            )
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -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>
+32 −0
Original line number Diff line number Diff line
@@ -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> =
@@ -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) {
@@ -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 {
+17 −3
Original line number Diff line number Diff line
@@ -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() {
        applicationScope.launch {
            if (userRepository.isSecondaryUserLogoutEnabled.value) {
            applicationScope.launch { userRepository.logOutSecondaryUser() }
                userRepository.logOutSecondaryUser()
            } else if (userRepository.isLogoutToSystemUserEnabled.value) {
                userRepository.logOutToSystemUser()
            }
        }
    }
}
Loading