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

Commit ea92f14c authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

UserRecord support in user interactor.

This is needed for backwards-compatibility purposes. Classes that extend
BaseUserSwitcherAdapter deal in UserRecord data items and are too
numerous and too risky to refactor to use the newer UserModel and
UserActionModel.

As part of this work, this CL extracts shared logic to instantiate
UserRecord instances from UserSwitcherControllerOldImpl so it can be
reused by UserInteractor.

Bug: 246631653
Test: unit tests included. Manually verified old implementation still
works across status bar, quick settings, bouncer dropdown, and
full-screen user switcher.

Change-Id: I2214b26700a539551cae84eb71b7b90d2e785bef
parent ae0fcebd
Loading
Loading
Loading
Loading
+40 −79
Original line number Diff line number Diff line
@@ -17,8 +17,6 @@ package com.android.systemui.statusbar.policy;

import static android.os.UserManager.SWITCHABILITY_STATUS_OK;

import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;

import android.annotation.UserIdInt;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -51,11 +49,9 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.users.UserCreatingDialog;
import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.R;
import com.android.systemui.SystemUISecondaryUserService;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
@@ -73,6 +69,8 @@ import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.user.data.source.UserRecord;
import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper;
import com.android.systemui.user.shared.model.UserActionModel;
import com.android.systemui.user.ui.dialog.AddUserDialog;
import com.android.systemui.user.ui.dialog.ExitGuestDialog;
import com.android.systemui.util.settings.GlobalSettings;
@@ -327,7 +325,6 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {

            for (UserInfo info : infos) {
                boolean isCurrent = currentId == info.id;
                boolean switchToEnabled = canSwitchUsers || isCurrent;
                if (!mUserSwitcherEnabled && !info.isPrimary()) {
                    continue;
                }
@@ -336,26 +333,22 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
                    if (info.isGuest()) {
                        // Tapping guest icon triggers remove and a user switch therefore
                        // the icon shouldn't be enabled even if the user is current
                        guestRecord = new UserRecord(info, null /* picture */,
                                true /* isGuest */, isCurrent, false /* isAddUser */,
                                false /* isRestricted */, canSwitchUsers,
                                false /* isAddSupervisedUser */, null /* enforcedAdmin */);
                        guestRecord = LegacyUserDataHelper.createRecord(
                                mContext,
                                mUserManager,
                                null /* picture */,
                                info,
                                isCurrent,
                                canSwitchUsers);
                    } else if (info.supportsSwitchToByUser()) {
                        Bitmap picture = bitmaps.get(info.id);
                        if (picture == null) {
                            picture = mUserManager.getUserIcon(info.id);

                            if (picture != null) {
                                int avatarSize = mContext.getResources()
                                        .getDimensionPixelSize(R.dimen.max_avatar_size);
                                picture = Bitmap.createScaledBitmap(
                                        picture, avatarSize, avatarSize, true);
                            }
                        }
                        records.add(new UserRecord(info, picture, false /* isGuest */,
                                isCurrent, false /* isAddUser */, false /* isRestricted */,
                                switchToEnabled, false /* isAddSupervisedUser */,
                                null /* enforcedAdmin */));
                        records.add(
                                LegacyUserDataHelper.createRecord(
                                        mContext,
                                        mUserManager,
                                        bitmaps.get(info.id),
                                        info,
                                        isCurrent,
                                        canSwitchUsers));
                    }
                }
            }
@@ -366,28 +359,20 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
                    // we will just use it as an indicator for "Resetting guest...".
                    // Otherwise, default to canSwitchUsers.
                    boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers;
                    guestRecord = new UserRecord(
                            null /* info */,
                            null /* picture */,
                            true /* isGuest */,
                            false /* isCurrent */,
                            false /* isAddUser */,
                    guestRecord = LegacyUserDataHelper.createRecord(
                            mContext,
                            currentId,
                            UserActionModel.ENTER_GUEST_MODE,
                            false /* isRestricted */,
                            isSwitchToGuestEnabled,
                            false /* isAddSupervisedUser */,
                            getEnforcedAdmin());
                            isSwitchToGuestEnabled);
                    records.add(guestRecord);
                } else if (canCreateGuest(guestRecord != null)) {
                    guestRecord = new UserRecord(
                            null /* info */,
                            null /* picture */,
                            true /* isGuest */,
                            false /* isCurrent */,
                            false /* isAddUser */,
                            createIsRestricted(),
                            canSwitchUsers,
                            false /* isAddSupervisedUser */,
                            getEnforcedAdmin());
                    guestRecord = LegacyUserDataHelper.createRecord(
                            mContext,
                            currentId,
                            UserActionModel.ENTER_GUEST_MODE,
                            false /* isRestricted */,
                            canSwitchUsers);
                    records.add(guestRecord);
                }
            } else {
@@ -395,31 +380,23 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
            }

            if (canCreateUser()) {
                UserRecord addUserRecord = new UserRecord(
                        null /* info */,
                        null /* picture */,
                        false /* isGuest */,
                        false /* isCurrent */,
                        true /* isAddUser */,
                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
                        mContext,
                        currentId,
                        UserActionModel.ADD_USER,
                        createIsRestricted(),
                        canSwitchUsers,
                        false /* isAddSupervisedUser */,
                        getEnforcedAdmin());
                records.add(addUserRecord);
                        canSwitchUsers);
                records.add(userRecord);
            }

            if (canCreateSupervisedUser()) {
                UserRecord addUserRecord = new UserRecord(
                        null /* info */,
                        null /* picture */,
                        false /* isGuest */,
                        false /* isCurrent */,
                        false /* isAddUser */,
                final UserRecord userRecord = LegacyUserDataHelper.createRecord(
                        mContext,
                        currentId,
                        UserActionModel.ADD_SUPERVISED_USER,
                        createIsRestricted(),
                        canSwitchUsers,
                        true /* isAddSupervisedUser */,
                        getEnforcedAdmin());
                records.add(addUserRecord);
                        canSwitchUsers);
                records.add(userRecord);
            }

            mUiExecutor.execute(() -> {
@@ -998,22 +975,6 @@ public class UserSwitcherControllerOldImpl implements UserSwitcherController {
        return mKeyguardStateController.isShowing();
    }

    @Nullable
    private EnforcedAdmin getEnforcedAdmin() {
        final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
                mContext,
                UserManager.DISALLOW_ADD_USER,
                mUserTracker.getUserId());
        if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(
                mContext,
                UserManager.DISALLOW_ADD_USER,
                mUserTracker.getUserId())) {
            return admin;
        } else {
            return null;
        }
    }

    private boolean shouldUseSimpleUserSwitcher() {
        int defaultSimpleUserSwitcher = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0;
+91 −0
Original line number Diff line number Diff line
@@ -46,7 +46,9 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.util.kotlin.pairwise
@@ -57,6 +59,8 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -65,6 +69,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

/** Encapsulates business logic to interact with user data and systems. */
@@ -234,6 +239,54 @@ constructor(
                    }
            }

    val userRecords: StateFlow<ArrayList<UserRecord>> =
        if (isNewImpl) {
            combine(
                    repository.userInfos,
                    repository.selectedUserInfo,
                    actions,
                    repository.userSwitcherSettings,
                ) { userInfos, selectedUserInfo, actionModels, settings ->
                    ArrayList(
                        userInfos.map {
                            toRecord(
                                userInfo = it,
                                selectedUserId = selectedUserInfo.id,
                            )
                        } +
                            actionModels.map {
                                toRecord(
                                    action = it,
                                    selectedUserId = selectedUserInfo.id,
                                    isAddFromLockscreenEnabled = settings.isAddUsersFromLockscreen,
                                )
                            }
                    )
                }
                .stateIn(
                    scope = applicationScope,
                    started = SharingStarted.Eagerly,
                    initialValue = ArrayList(),
                )
        } else {
            MutableStateFlow(ArrayList())
        }

    val selectedUserRecord: StateFlow<UserRecord?> =
        if (isNewImpl) {
            repository.selectedUserInfo
                .map { selectedUserInfo ->
                    toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id)
                }
                .stateIn(
                    scope = applicationScope,
                    started = SharingStarted.Eagerly,
                    initialValue = null,
                )
        } else {
            MutableStateFlow(null)
        }

    /** Whether the device is configured to always have a guest user available. */
    val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated

@@ -407,6 +460,44 @@ constructor(
        }
    }

    private suspend fun toRecord(
        userInfo: UserInfo,
        selectedUserId: Int,
    ): UserRecord {
        return LegacyUserDataHelper.createRecord(
            context = applicationContext,
            manager = manager,
            userInfo = userInfo,
            picture = null,
            isCurrent = userInfo.id == selectedUserId,
            canSwitchUsers = canSwitchUsers(selectedUserId),
        )
    }

    private suspend fun toRecord(
        action: UserActionModel,
        selectedUserId: Int,
        isAddFromLockscreenEnabled: Boolean,
    ): UserRecord {
        return LegacyUserDataHelper.createRecord(
            context = applicationContext,
            selectedUserId = selectedUserId,
            actionType = action,
            isRestricted =
                if (action == UserActionModel.ENTER_GUEST_MODE) {
                    // Entering guest mode is never restricted, so it's allowed to happen from the
                    // lockscreen even if the "add from lockscreen" system setting is off.
                    false
                } else {
                    !isAddFromLockscreenEnabled
                },
            isSwitchToEnabled =
                canSwitchUsers(selectedUserId) &&
                    // If the user is auto-created is must not be currently resetting.
                    !(isGuestUserAutoCreated && isGuestUserResetting),
        )
    }

    private fun exitGuestUser(
        @UserIdInt guestUserId: Int,
        @UserIdInt targetUserId: Int,
+135 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.android.systemui.user.legacyhelper.data

import android.content.Context
import android.content.pm.UserInfo
import android.graphics.Bitmap
import android.os.UserManager
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
import com.android.settingslib.RestrictedLockUtilsInternal
import com.android.systemui.R
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.shared.model.UserActionModel

/**
 * Defines utility functions for helping with legacy data code for users.
 *
 * We need these to avoid code duplication between logic inside the UserSwitcherController and in
 * modern architecture classes such as repositories, interactors, and view-models. If we ever
 * simplify UserSwitcherController (or delete it), the code here could be moved into its call-sites.
 */
object LegacyUserDataHelper {

    @JvmStatic
    fun createRecord(
        context: Context,
        manager: UserManager,
        picture: Bitmap?,
        userInfo: UserInfo,
        isCurrent: Boolean,
        canSwitchUsers: Boolean,
    ): UserRecord {
        val isGuest = userInfo.isGuest
        return UserRecord(
            info = userInfo,
            picture =
                getPicture(
                    manager = manager,
                    context = context,
                    userInfo = userInfo,
                    picture = picture,
                ),
            isGuest = isGuest,
            isCurrent = isCurrent,
            isSwitchToEnabled = canSwitchUsers || (isCurrent && !isGuest),
        )
    }

    @JvmStatic
    fun createRecord(
        context: Context,
        selectedUserId: Int,
        actionType: UserActionModel,
        isRestricted: Boolean,
        isSwitchToEnabled: Boolean,
    ): UserRecord {
        return UserRecord(
            isGuest = actionType == UserActionModel.ENTER_GUEST_MODE,
            isAddUser = actionType == UserActionModel.ADD_USER,
            isAddSupervisedUser = actionType == UserActionModel.ADD_SUPERVISED_USER,
            isRestricted = isRestricted,
            isSwitchToEnabled = isSwitchToEnabled,
            enforcedAdmin =
                getEnforcedAdmin(
                    context = context,
                    selectedUserId = selectedUserId,
                ),
        )
    }

    private fun getEnforcedAdmin(
        context: Context,
        selectedUserId: Int,
    ): EnforcedAdmin? {
        val admin =
            RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
                context,
                UserManager.DISALLOW_ADD_USER,
                selectedUserId,
            )
                ?: return null

        return if (
            !RestrictedLockUtilsInternal.hasBaseUserRestriction(
                context,
                UserManager.DISALLOW_ADD_USER,
                selectedUserId,
            )
        ) {
            admin
        } else {
            null
        }
    }

    private fun getPicture(
        context: Context,
        manager: UserManager,
        userInfo: UserInfo,
        picture: Bitmap?,
    ): Bitmap? {
        if (userInfo.isGuest) {
            return null
        }

        if (picture != null) {
            return picture
        }

        val unscaledOrNull = manager.getUserIcon(userInfo.id) ?: return null

        val avatarSize = context.resources.getDimensionPixelSize(R.dimen.max_avatar_size)
        return Bitmap.createScaledBitmap(
            unscaledOrNull,
            avatarSize,
            avatarSize,
            /* filter= */ true,
        )
    }
}
+106 −2
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import com.android.internal.R.drawable.ic_account_circle
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
@@ -62,6 +63,7 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
        super.setUp()

        overrideResource(R.drawable.ic_account_circle, GUEST_ICON)
        overrideResource(R.dimen.max_avatar_size, 10)
        overrideResource(
            com.android.internal.R.string.config_supervisedUserCreationPackage,
            SUPERVISED_USER_CREATION_APP_PACKAGE,
@@ -470,6 +472,49 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
            assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount)
        }

    @Test
    fun userRecords() =
        runBlocking(IMMEDIATE) {
            val userInfos = createUserInfos(count = 3, includeGuest = false)
            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
            userRepository.setUserInfos(userInfos)
            userRepository.setSelectedUserInfo(userInfos[0])
            keyguardRepository.setKeyguardShowing(false)

            testCoroutineScope.advanceUntilIdle()

            assertRecords(
                records = underTest.userRecords.value,
                userIds = listOf(0, 1, 2),
                selectedUserIndex = 0,
                includeGuest = false,
                expectedActions =
                    listOf(
                        UserActionModel.ENTER_GUEST_MODE,
                        UserActionModel.ADD_USER,
                        UserActionModel.ADD_SUPERVISED_USER,
                    ),
            )
        }

    @Test
    fun selectedUserRecord() =
        runBlocking(IMMEDIATE) {
            val userInfos = createUserInfos(count = 3, includeGuest = true)
            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
            userRepository.setUserInfos(userInfos)
            userRepository.setSelectedUserInfo(userInfos[0])
            keyguardRepository.setKeyguardShowing(false)

            assertRecordForUser(
                record = underTest.selectedUserRecord.value,
                id = 0,
                hasPicture = true,
                isCurrent = true,
                isSwitchToEnabled = true,
            )
        }

    private fun assertUsers(
        models: List<UserModel>?,
        count: Int,
@@ -502,6 +547,65 @@ class UserInteractorRefactoredTest : UserInteractorTest() {
        assertThat(model.isGuest).isEqualTo(isGuest)
    }

    private fun assertRecords(
        records: List<UserRecord>,
        userIds: List<Int>,
        selectedUserIndex: Int = 0,
        includeGuest: Boolean = false,
        expectedActions: List<UserActionModel> = emptyList(),
    ) {
        assertThat(records.size >= userIds.size).isTrue()
        userIds.indices.forEach { userIndex ->
            val record = records[userIndex]
            assertThat(record.info).isNotNull()
            val isGuest = includeGuest && userIndex == userIds.size - 1
            assertRecordForUser(
                record = record,
                id = userIds[userIndex],
                hasPicture = !isGuest,
                isCurrent = userIndex == selectedUserIndex,
                isGuest = isGuest,
                isSwitchToEnabled = true,
            )
        }

        assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
        (userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
            val record = records[actionIndex]
            assertThat(record.info).isNull()
            assertRecordForAction(
                record = record,
                type = expectedActions[actionIndex - userIds.size],
            )
        }
    }

    private fun assertRecordForUser(
        record: UserRecord?,
        id: Int? = null,
        hasPicture: Boolean = false,
        isCurrent: Boolean = false,
        isGuest: Boolean = false,
        isSwitchToEnabled: Boolean = false,
    ) {
        checkNotNull(record)
        assertThat(record.info?.id).isEqualTo(id)
        assertThat(record.picture != null).isEqualTo(hasPicture)
        assertThat(record.isCurrent).isEqualTo(isCurrent)
        assertThat(record.isGuest).isEqualTo(isGuest)
        assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
    }

    private fun assertRecordForAction(
        record: UserRecord,
        type: UserActionModel,
    ) {
        assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
        assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
        assertThat(record.isAddSupervisedUser)
            .isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
    }

    private fun createUserInfos(
        count: Int,
        includeGuest: Boolean,
@@ -547,8 +651,8 @@ class UserInteractorRefactoredTest : UserInteractorTest() {

    companion object {
        private val IMMEDIATE = Dispatchers.Main.immediate
        private val ICON: Bitmap = mock()
        private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
        private val GUEST_ICON: Drawable = mock()
        private val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
        private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ abstract class UserInteractorTest : SysuiTestCase() {

    protected lateinit var underTest: UserInteractor

    protected lateinit var testCoroutineScope: TestCoroutineScope
    protected lateinit var userRepository: FakeUserRepository
    protected lateinit var keyguardRepository: FakeKeyguardRepository
    protected lateinit var telephonyRepository: FakeTelephonyRepository
@@ -61,10 +62,10 @@ abstract class UserInteractorTest : SysuiTestCase() {
        userRepository = FakeUserRepository()
        keyguardRepository = FakeKeyguardRepository()
        telephonyRepository = FakeTelephonyRepository()
        val applicationScope = TestCoroutineScope()
        testCoroutineScope = TestCoroutineScope()
        val refreshUsersScheduler =
            RefreshUsersScheduler(
                applicationScope = applicationScope,
                applicationScope = testCoroutineScope,
                mainDispatcher = IMMEDIATE,
                repository = userRepository,
            )
@@ -83,7 +84,7 @@ abstract class UserInteractorTest : SysuiTestCase() {
                        set(Flags.REFACTORED_USER_SWITCHER_CONTROLLER, isRefactored())
                    },
                manager = manager,
                applicationScope = applicationScope,
                applicationScope = testCoroutineScope,
                telephonyInteractor =
                    TelephonyInteractor(
                        repository = telephonyRepository,
@@ -95,7 +96,7 @@ abstract class UserInteractorTest : SysuiTestCase() {
                guestUserInteractor =
                    GuestUserInteractor(
                        applicationContext = context,
                        applicationScope = applicationScope,
                        applicationScope = testCoroutineScope,
                        mainDispatcher = IMMEDIATE,
                        backgroundDispatcher = IMMEDIATE,
                        manager = manager,