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

Commit 3404a179 authored by Denis Kuznetsov's avatar Denis Kuznetsov
Browse files

Add logout to System user logic

Flag: EXEMPT Desktop-only change
Bug: 375384623
Bug: 377493351
Test: atest UserLogoutInteractorTest
Test: Manually tested
Change-Id: Ie85961f5b057e0b8effa13a84d26ebac9347cd52
parent 44371a90
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