diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt deleted file mode 100644 index 4d94bab6c26c8..0000000000000 --- a/packages/SystemUI/compose/features/src/com/android/systemui/user/ui/compose/UserSwitcherScreen.kt +++ /dev/null @@ -1,392 +0,0 @@ -/* - * 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.ui.compose - -import android.graphics.drawable.Drawable -import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.sizeIn -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.asImageBitmap -import androidx.compose.ui.graphics.painter.ColorPainter -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.core.graphics.drawable.toBitmap -import com.android.systemui.common.ui.compose.load -import com.android.systemui.compose.SysUiOutlinedButton -import com.android.systemui.compose.SysUiTextButton -import com.android.systemui.compose.features.R -import com.android.systemui.compose.theme.LocalAndroidColorScheme -import com.android.systemui.user.ui.viewmodel.UserActionViewModel -import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel -import com.android.systemui.user.ui.viewmodel.UserViewModel -import java.lang.Integer.min -import kotlin.math.ceil - -@Composable -fun UserSwitcherScreen( - viewModel: UserSwitcherViewModel, - onFinished: () -> Unit, - modifier: Modifier = Modifier, -) { - val isFinishRequested: Boolean by viewModel.isFinishRequested.collectAsState(false) - val users: List by viewModel.users.collectAsState(emptyList()) - val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1) - val menuActions: List by viewModel.menu.collectAsState(emptyList()) - val isOpenMenuButtonVisible: Boolean by viewModel.isOpenMenuButtonVisible.collectAsState(false) - val isMenuVisible: Boolean by viewModel.isMenuVisible.collectAsState(false) - - UserSwitcherScreenStateless( - isFinishRequested = isFinishRequested, - users = users, - maxUserColumns = maxUserColumns, - menuActions = menuActions, - isOpenMenuButtonVisible = isOpenMenuButtonVisible, - isMenuVisible = isMenuVisible, - onMenuClosed = viewModel::onMenuClosed, - onOpenMenuButtonClicked = viewModel::onOpenMenuButtonClicked, - onCancelButtonClicked = viewModel::onCancelButtonClicked, - onFinished = { - onFinished() - viewModel.onFinished() - }, - modifier = modifier, - ) -} - -@Composable -private fun UserSwitcherScreenStateless( - isFinishRequested: Boolean, - users: List, - maxUserColumns: Int, - menuActions: List, - isOpenMenuButtonVisible: Boolean, - isMenuVisible: Boolean, - onMenuClosed: () -> Unit, - onOpenMenuButtonClicked: () -> Unit, - onCancelButtonClicked: () -> Unit, - onFinished: () -> Unit, - modifier: Modifier = Modifier, -) { - LaunchedEffect(isFinishRequested) { - if (isFinishRequested) { - onFinished() - } - } - - Box( - modifier = - modifier - .fillMaxSize() - .padding( - horizontal = 60.dp, - vertical = 40.dp, - ), - ) { - UserGrid( - users = users, - maxUserColumns = maxUserColumns, - modifier = Modifier.align(Alignment.Center), - ) - - Buttons( - menuActions = menuActions, - isOpenMenuButtonVisible = isOpenMenuButtonVisible, - isMenuVisible = isMenuVisible, - onMenuClosed = onMenuClosed, - onOpenMenuButtonClicked = onOpenMenuButtonClicked, - onCancelButtonClicked = onCancelButtonClicked, - modifier = Modifier.align(Alignment.BottomEnd), - ) - } -} - -@Composable -private fun UserGrid( - users: List, - maxUserColumns: Int, - modifier: Modifier = Modifier, -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(44.dp), - modifier = modifier, - ) { - val rowCount = ceil(users.size / maxUserColumns.toFloat()).toInt() - (0 until rowCount).forEach { rowIndex -> - Row( - horizontalArrangement = Arrangement.spacedBy(64.dp), - modifier = modifier, - ) { - val fromIndex = rowIndex * maxUserColumns - val toIndex = min(users.size, (rowIndex + 1) * maxUserColumns) - users.subList(fromIndex, toIndex).forEach { user -> - UserItem( - viewModel = user, - ) - } - } - } - } -} - -@Composable -private fun UserItem( - viewModel: UserViewModel, -) { - val onClicked = viewModel.onClicked - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = - if (onClicked != null) { - Modifier.clickable { onClicked() } - } else { - Modifier - } - .alpha(viewModel.alpha), - ) { - Box { - UserItemBackground(modifier = Modifier.align(Alignment.Center).size(222.dp)) - - UserItemIcon( - image = viewModel.image, - isSelectionMarkerVisible = viewModel.isSelectionMarkerVisible, - modifier = Modifier.align(Alignment.Center).size(222.dp) - ) - } - - // User name - val text = viewModel.name.load() - if (text != null) { - // We use the box to center-align the text vertically as that is not possible with Text - // alone. - Box( - modifier = Modifier.size(width = 222.dp, height = 48.dp), - ) { - Text( - text = text, - style = MaterialTheme.typography.titleLarge, - color = colorResource(com.android.internal.R.color.system_neutral1_50), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.align(Alignment.Center), - ) - } - } - } -} - -@Composable -private fun UserItemBackground( - modifier: Modifier = Modifier, -) { - Image( - painter = ColorPainter(LocalAndroidColorScheme.current.colorBackground), - contentDescription = null, - modifier = modifier.clip(CircleShape), - ) -} - -@Composable -private fun UserItemIcon( - image: Drawable, - isSelectionMarkerVisible: Boolean, - modifier: Modifier = Modifier, -) { - Image( - bitmap = image.toBitmap().asImageBitmap(), - contentDescription = null, - modifier = - if (isSelectionMarkerVisible) { - // Draws a ring - modifier.border( - width = 8.dp, - color = LocalAndroidColorScheme.current.colorAccentPrimary, - shape = CircleShape, - ) - } else { - modifier - } - .padding(16.dp) - .clip(CircleShape) - ) -} - -@Composable -private fun Buttons( - menuActions: List, - isOpenMenuButtonVisible: Boolean, - isMenuVisible: Boolean, - onMenuClosed: () -> Unit, - onOpenMenuButtonClicked: () -> Unit, - onCancelButtonClicked: () -> Unit, - modifier: Modifier = Modifier, -) { - Row( - modifier = modifier, - ) { - // Cancel button. - SysUiTextButton( - onClick = onCancelButtonClicked, - ) { - Text(stringResource(R.string.cancel)) - } - - // "Open menu" button. - if (isOpenMenuButtonVisible) { - Spacer(modifier = Modifier.width(8.dp)) - // To properly use a DropdownMenu in Compose, we need to wrap the button that opens it - // and the menu itself in a Box. - Box { - SysUiOutlinedButton( - onClick = onOpenMenuButtonClicked, - ) { - Text(stringResource(R.string.add)) - } - Menu( - viewModel = menuActions, - isMenuVisible = isMenuVisible, - onMenuClosed = onMenuClosed, - ) - } - } - } -} - -@Composable -private fun Menu( - viewModel: List, - isMenuVisible: Boolean, - onMenuClosed: () -> Unit, - modifier: Modifier = Modifier, -) { - val maxItemWidth = LocalConfiguration.current.screenWidthDp.dp / 4 - DropdownMenu( - expanded = isMenuVisible, - onDismissRequest = onMenuClosed, - modifier = - modifier.background( - color = MaterialTheme.colorScheme.inverseOnSurface, - ), - ) { - viewModel.forEachIndexed { index, action -> - MenuItem( - viewModel = action, - onClicked = { action.onClicked() }, - topPadding = - if (index == 0) { - 16.dp - } else { - 0.dp - }, - bottomPadding = - if (index == viewModel.size - 1) { - 16.dp - } else { - 0.dp - }, - modifier = Modifier.sizeIn(maxWidth = maxItemWidth), - ) - } - } -} - -@Composable -private fun MenuItem( - viewModel: UserActionViewModel, - onClicked: () -> Unit, - topPadding: Dp, - bottomPadding: Dp, - modifier: Modifier = Modifier, -) { - val context = LocalContext.current - val density = LocalDensity.current - - val icon = - remember(viewModel.iconResourceId) { - val drawable = - checkNotNull(AppCompatResources.getDrawable(context, viewModel.iconResourceId)) - val size = with(density) { 20.dp.toPx() }.toInt() - drawable - .toBitmap( - width = size, - height = size, - ) - .asImageBitmap() - } - - DropdownMenuItem( - text = { - Text( - text = stringResource(viewModel.textResourceId), - style = MaterialTheme.typography.bodyMedium, - ) - }, - onClick = onClicked, - leadingIcon = { - Spacer(modifier = Modifier.width(10.dp)) - Image( - bitmap = icon, - contentDescription = null, - ) - }, - modifier = - modifier - .heightIn( - min = 56.dp, - ) - .padding( - start = 18.dp, - end = 65.dp, - top = topPadding, - bottom = bottomPadding, - ), - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 19fd25db8349a..fa2269777b788 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -114,28 +114,6 @@ object Flags { // TODO(b/254512385): Tracking Bug @JvmField val MODERN_BOUNCER = releasedFlag(208, "modern_bouncer") - /** - * Whether the user interactor and repository should use `UserSwitcherController`. - * - * If this is `false`, the interactor and repo skip the controller and directly access the - * framework APIs. - */ - // TODO(b/254513286): Tracking Bug - val USER_INTERACTOR_AND_REPO_USE_CONTROLLER = - unreleasedFlag(210, "user_interactor_and_repo_use_controller") - - /** - * Whether `UserSwitcherController` should use the user interactor. - * - * When this is `true`, the controller does not directly access framework APIs. Instead, it goes - * through the interactor. - * - * Note: do not set this to true if [.USER_INTERACTOR_AND_REPO_USE_CONTROLLER] is `true` as it - * would created a cycle between controller -> interactor -> controller. - */ - // TODO(b/254513102): Tracking Bug - val USER_CONTROLLER_USES_INTERACTOR = releasedFlag(211, "user_controller_uses_interactor") - /** * Whether the clock on a wide lock screen should use the new "stepping" animation for moving * the digits when the clock moves. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 294bd997bd51a..a79369e55ef26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1148,7 +1148,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot. mNotificationIconAreaController.setupShelf(mNotificationShelfController); mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator); - mUserSwitcherController.init(mNotificationShadeWindowView); // Allow plugins to reference DarkIconDispatcher and StatusBarStateController mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class); @@ -4286,7 +4285,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } // TODO: Bring these out of CentralSurfaces. mUserInfoControllerImpl.onDensityOrFontScaleChanged(); - mUserSwitcherController.onDensityOrFontScaleChanged(); mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); mHeadsUpManager.onDensityOrFontScaleChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt index cf4106c508cb2..68d30d3f3d1e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt @@ -21,7 +21,6 @@ import android.graphics.ColorFilter import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter import android.graphics.drawable.Drawable -import android.os.UserHandle import android.widget.BaseAdapter import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower import com.android.systemui.user.data.source.UserRecord @@ -84,7 +83,7 @@ protected constructor( } fun refresh() { - controller.refreshUsers(UserHandle.USER_NULL) + controller.refreshUsers() } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt index 146b222c94ceb..bdb656b9d2d55 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.kt @@ -14,35 +14,74 @@ * limitations under the License. * */ + package com.android.systemui.statusbar.policy -import android.annotation.UserIdInt +import android.content.Context import android.content.Intent import android.view.View -import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower import com.android.systemui.user.data.source.UserRecord +import com.android.systemui.user.domain.interactor.GuestUserInteractor +import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper +import dagger.Lazy +import java.io.PrintWriter import java.lang.ref.WeakReference -import kotlinx.coroutines.flow.Flow +import javax.inject.Inject -/** Defines interface for a class that provides user switching functionality and state. */ -interface UserSwitcherController : Dumpable { +/** Access point into multi-user switching logic. */ +@Deprecated("Use UserInteractor or GuestUserInteractor instead.") +@SysUISingleton +class UserSwitcherController +@Inject +constructor( + @Application private val applicationContext: Context, + private val userInteractorLazy: Lazy, + private val guestUserInteractorLazy: Lazy, + private val keyguardInteractorLazy: Lazy, + private val activityStarter: ActivityStarter, +) { + + /** Defines interface for classes that can be called back when the user is switched. */ + fun interface UserSwitchCallback { + /** Notifies that the user has switched. */ + fun onUserSwitched() + } + + private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() } + private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() } + private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() } + + private val callbackCompatMap = mutableMapOf() /** The current list of [UserRecord]. */ val users: ArrayList + get() = userInteractor.userRecords.value /** Whether the user switcher experience should use the simple experience. */ val isSimpleUserSwitcher: Boolean - - /** Require a view for jank detection */ - fun init(view: View) + get() = userInteractor.isSimpleUserSwitcher /** The [UserRecord] of the current user or `null` when none. */ val currentUserRecord: UserRecord? + get() = userInteractor.selectedUserRecord.value /** The name of the current user of the device or `null`, when none is selected. */ val currentUserName: String? + get() = + currentUserRecord?.let { + LegacyUserUiHelper.getUserRecordName( + context = applicationContext, + record = it, + isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated, + isGuestUserResetting = userInteractor.isGuestUserResetting, + ) + } /** * Notifies that a user has been selected. @@ -55,34 +94,40 @@ interface UserSwitcherController : Dumpable { * @param userId The ID of the user to switch to. * @param dialogShower An optional [DialogShower] in case we need to show dialogs. */ - fun onUserSelected(userId: Int, dialogShower: DialogShower?) - - /** Whether it is allowed to add users while the device is locked. */ - val isAddUsersFromLockScreenEnabled: Flow + fun onUserSelected(userId: Int, dialogShower: DialogShower?) { + userInteractor.selectUser(userId, dialogShower) + } /** Whether the guest user is configured to always be present on the device. */ val isGuestUserAutoCreated: Boolean + get() = userInteractor.isGuestUserAutoCreated /** Whether the guest user is currently being reset. */ val isGuestUserResetting: Boolean - - /** Creates and switches to the guest user. */ - fun createAndSwitchToGuestUser(dialogShower: DialogShower?) - - /** Shows the add user dialog. */ - fun showAddUserDialog(dialogShower: DialogShower?) - - /** Starts an activity to add a supervised user to the device. */ - fun startSupervisedUserActivity() - - /** Notifies when the display density or font scale has changed. */ - fun onDensityOrFontScaleChanged() + get() = userInteractor.isGuestUserResetting /** Registers an adapter to notify when the users change. */ - fun addAdapter(adapter: WeakReference) + fun addAdapter(adapter: WeakReference) { + userInteractor.addCallback( + object : UserInteractor.UserCallback { + override fun isEvictable(): Boolean { + return adapter.get() == null + } + + override fun onUserStateChanged() { + adapter.get()?.notifyDataSetChanged() + } + } + ) + } /** Notifies the item for a user has been clicked. */ - fun onUserListItemClicked(record: UserRecord, dialogShower: DialogShower?) + fun onUserListItemClicked( + record: UserRecord, + dialogShower: DialogShower?, + ) { + userInteractor.onRecordSelected(record, dialogShower) + } /** * Removes guest user and switches to target user. The guest must be the current user and its id @@ -103,7 +148,12 @@ interface UserSwitcherController : Dumpable { * @param targetUserId id of the user to switch to after guest is removed. If * `UserHandle.USER_NULL`, then switch immediately to the newly created guest user. */ - fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) + fun removeGuestUser(guestUserId: Int, targetUserId: Int) { + userInteractor.removeGuestUser( + guestUserId = guestUserId, + targetUserId = targetUserId, + ) + } /** * Exits guest user and switches to previous non-guest user. The guest must be the current user. @@ -114,43 +164,58 @@ interface UserSwitcherController : Dumpable { * @param forceRemoveGuestOnExit true: remove guest before switching user, false: remove guest * only if its ephemeral, else keep guest */ - fun exitGuestUser( - @UserIdInt guestUserId: Int, - @UserIdInt targetUserId: Int, - forceRemoveGuestOnExit: Boolean - ) + fun exitGuestUser(guestUserId: Int, targetUserId: Int, forceRemoveGuestOnExit: Boolean) { + userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) + } /** * Guarantee guest is present only if the device is provisioned. Otherwise, create a content * observer to wait until the device is provisioned, then schedule the guest creation. */ - fun schedulePostBootGuestCreation() + fun schedulePostBootGuestCreation() { + guestUserInteractor.onDeviceBootCompleted() + } /** Whether keyguard is showing. */ val isKeyguardShowing: Boolean + get() = keyguardInteractor.isKeyguardShowing() /** Starts an activity with the given [Intent]. */ - fun startActivity(intent: Intent) + fun startActivity(intent: Intent) { + activityStarter.startActivity(intent, /* dismissShade= */ true) + } /** * Refreshes users from UserManager. * * The pictures are only loaded if they have not been loaded yet. - * - * @param forcePictureLoadForId forces the picture of the given user to be reloaded. */ - fun refreshUsers(forcePictureLoadForId: Int) + fun refreshUsers() { + userInteractor.refreshUsers() + } /** Adds a subscriber to when user switches. */ - fun addUserSwitchCallback(callback: UserSwitchCallback) + fun addUserSwitchCallback(callback: UserSwitchCallback) { + val interactorCallback = + object : UserInteractor.UserCallback { + override fun onUserStateChanged() { + callback.onUserSwitched() + } + } + callbackCompatMap[callback] = interactorCallback + userInteractor.addCallback(interactorCallback) + } /** Removes a previously-added subscriber. */ - fun removeUserSwitchCallback(callback: UserSwitchCallback) + fun removeUserSwitchCallback(callback: UserSwitchCallback) { + val interactorCallback = callbackCompatMap.remove(callback) + if (interactorCallback != null) { + userInteractor.removeCallback(interactorCallback) + } + } - /** Defines interface for classes that can be called back when the user is switched. */ - fun interface UserSwitchCallback { - /** Notifies that the user has switched. */ - fun onUserSwitched() + fun dump(pw: PrintWriter, args: Array) { + userInteractor.dump(pw) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt deleted file mode 100644 index 935fc7f10198d..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerImpl.kt +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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.statusbar.policy - -import android.content.Context -import android.content.Intent -import android.view.View -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.qs.user.UserSwitchDialogController -import com.android.systemui.user.data.source.UserRecord -import com.android.systemui.user.domain.interactor.GuestUserInteractor -import com.android.systemui.user.domain.interactor.UserInteractor -import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper -import dagger.Lazy -import java.io.PrintWriter -import java.lang.ref.WeakReference -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow - -/** Implementation of [UserSwitcherController]. */ -@SysUISingleton -class UserSwitcherControllerImpl -@Inject -constructor( - @Application private val applicationContext: Context, - flags: FeatureFlags, - @Suppress("DEPRECATION") private val oldImpl: Lazy, - private val userInteractorLazy: Lazy, - private val guestUserInteractorLazy: Lazy, - private val keyguardInteractorLazy: Lazy, - private val activityStarter: ActivityStarter, -) : UserSwitcherController { - - private val useInteractor: Boolean = - flags.isEnabled(Flags.USER_CONTROLLER_USES_INTERACTOR) && - !flags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER) - private val _oldImpl: UserSwitcherControllerOldImpl - get() = oldImpl.get() - private val userInteractor: UserInteractor by lazy { userInteractorLazy.get() } - private val guestUserInteractor: GuestUserInteractor by lazy { guestUserInteractorLazy.get() } - private val keyguardInteractor: KeyguardInteractor by lazy { keyguardInteractorLazy.get() } - - private val callbackCompatMap = - mutableMapOf() - - private fun notSupported(): Nothing { - error("Not supported in the new implementation!") - } - - override val users: ArrayList - get() = - if (useInteractor) { - userInteractor.userRecords.value - } else { - _oldImpl.users - } - - override val isSimpleUserSwitcher: Boolean - get() = - if (useInteractor) { - userInteractor.isSimpleUserSwitcher - } else { - _oldImpl.isSimpleUserSwitcher - } - - override fun init(view: View) { - if (!useInteractor) { - _oldImpl.init(view) - } - } - - override val currentUserRecord: UserRecord? - get() = - if (useInteractor) { - userInteractor.selectedUserRecord.value - } else { - _oldImpl.currentUserRecord - } - - override val currentUserName: String? - get() = - if (useInteractor) { - currentUserRecord?.let { - LegacyUserUiHelper.getUserRecordName( - context = applicationContext, - record = it, - isGuestUserAutoCreated = userInteractor.isGuestUserAutoCreated, - isGuestUserResetting = userInteractor.isGuestUserResetting, - ) - } - } else { - _oldImpl.currentUserName - } - - override fun onUserSelected( - userId: Int, - dialogShower: UserSwitchDialogController.DialogShower? - ) { - if (useInteractor) { - userInteractor.selectUser(userId, dialogShower) - } else { - _oldImpl.onUserSelected(userId, dialogShower) - } - } - - override val isAddUsersFromLockScreenEnabled: Flow - get() = - if (useInteractor) { - notSupported() - } else { - _oldImpl.isAddUsersFromLockScreenEnabled - } - - override val isGuestUserAutoCreated: Boolean - get() = - if (useInteractor) { - userInteractor.isGuestUserAutoCreated - } else { - _oldImpl.isGuestUserAutoCreated - } - - override val isGuestUserResetting: Boolean - get() = - if (useInteractor) { - userInteractor.isGuestUserResetting - } else { - _oldImpl.isGuestUserResetting - } - - override fun createAndSwitchToGuestUser( - dialogShower: UserSwitchDialogController.DialogShower?, - ) { - if (useInteractor) { - notSupported() - } else { - _oldImpl.createAndSwitchToGuestUser(dialogShower) - } - } - - override fun showAddUserDialog(dialogShower: UserSwitchDialogController.DialogShower?) { - if (useInteractor) { - notSupported() - } else { - _oldImpl.showAddUserDialog(dialogShower) - } - } - - override fun startSupervisedUserActivity() { - if (useInteractor) { - notSupported() - } else { - _oldImpl.startSupervisedUserActivity() - } - } - - override fun onDensityOrFontScaleChanged() { - if (!useInteractor) { - _oldImpl.onDensityOrFontScaleChanged() - } - } - - override fun addAdapter(adapter: WeakReference) { - if (useInteractor) { - userInteractor.addCallback( - object : UserInteractor.UserCallback { - override fun isEvictable(): Boolean { - return adapter.get() == null - } - - override fun onUserStateChanged() { - adapter.get()?.notifyDataSetChanged() - } - } - ) - } else { - _oldImpl.addAdapter(adapter) - } - } - - override fun onUserListItemClicked( - record: UserRecord, - dialogShower: UserSwitchDialogController.DialogShower?, - ) { - if (useInteractor) { - userInteractor.onRecordSelected(record, dialogShower) - } else { - _oldImpl.onUserListItemClicked(record, dialogShower) - } - } - - override fun removeGuestUser(guestUserId: Int, targetUserId: Int) { - if (useInteractor) { - userInteractor.removeGuestUser( - guestUserId = guestUserId, - targetUserId = targetUserId, - ) - } else { - _oldImpl.removeGuestUser(guestUserId, targetUserId) - } - } - - override fun exitGuestUser( - guestUserId: Int, - targetUserId: Int, - forceRemoveGuestOnExit: Boolean - ) { - if (useInteractor) { - userInteractor.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) - } else { - _oldImpl.exitGuestUser(guestUserId, targetUserId, forceRemoveGuestOnExit) - } - } - - override fun schedulePostBootGuestCreation() { - if (useInteractor) { - guestUserInteractor.onDeviceBootCompleted() - } else { - _oldImpl.schedulePostBootGuestCreation() - } - } - - override val isKeyguardShowing: Boolean - get() = - if (useInteractor) { - keyguardInteractor.isKeyguardShowing() - } else { - _oldImpl.isKeyguardShowing - } - - override fun startActivity(intent: Intent) { - if (useInteractor) { - activityStarter.startActivity(intent, /* dismissShade= */ true) - } else { - _oldImpl.startActivity(intent) - } - } - - override fun refreshUsers(forcePictureLoadForId: Int) { - if (useInteractor) { - userInteractor.refreshUsers() - } else { - _oldImpl.refreshUsers(forcePictureLoadForId) - } - } - - override fun addUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { - if (useInteractor) { - val interactorCallback = - object : UserInteractor.UserCallback { - override fun onUserStateChanged() { - callback.onUserSwitched() - } - } - callbackCompatMap[callback] = interactorCallback - userInteractor.addCallback(interactorCallback) - } else { - _oldImpl.addUserSwitchCallback(callback) - } - } - - override fun removeUserSwitchCallback(callback: UserSwitcherController.UserSwitchCallback) { - if (useInteractor) { - val interactorCallback = callbackCompatMap.remove(callback) - if (interactorCallback != null) { - userInteractor.removeCallback(interactorCallback) - } - } else { - _oldImpl.removeUserSwitchCallback(callback) - } - } - - override fun dump(pw: PrintWriter, args: Array) { - if (useInteractor) { - userInteractor.dump(pw) - } else { - _oldImpl.dump(pw, args) - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java deleted file mode 100644 index c294c370a6011..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImpl.java +++ /dev/null @@ -1,1063 +0,0 @@ -/* - * Copyright (C) 2014 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.statusbar.policy; - -import static android.os.UserManager.SWITCHABILITY_STATUS_OK; - -import android.annotation.UserIdInt; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.IActivityManager; -import android.app.admin.DevicePolicyManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.UserInfo; -import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.os.Handler; -import android.os.RemoteException; -import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; -import android.telephony.TelephonyCallback; -import android.text.TextUtils; -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import android.view.View; -import android.view.WindowManagerGlobal; -import android.widget.Toast; - -import androidx.annotation.Nullable; - -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.keyguard.KeyguardUpdateMonitor; -import com.android.settingslib.users.UserCreatingDialog; -import com.android.systemui.GuestResetOrExitSessionReceiver; -import com.android.systemui.GuestResumeSessionReceiver; -import com.android.systemui.SystemUISecondaryUserService; -import com.android.systemui.animation.DialogCuj; -import com.android.systemui.animation.DialogLaunchAnimator; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.broadcast.BroadcastSender; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Background; -import com.android.systemui.dagger.qualifiers.LongRunning; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.qs.QSUserSwitcherEvent; -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; -import com.android.systemui.util.settings.SecureSettings; - -import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -import javax.inject.Inject; - -import kotlinx.coroutines.flow.Flow; -import kotlinx.coroutines.flow.MutableStateFlow; -import kotlinx.coroutines.flow.StateFlowKt; - -/** - * Old implementation. Keeps a list of all users on the device for user switching. - * - * @deprecated This is the old implementation. Please depend on {@link UserSwitcherController} - * instead. - */ -@Deprecated -@SysUISingleton -public class UserSwitcherControllerOldImpl implements UserSwitcherController { - - private static final String TAG = "UserSwitcherController"; - private static final boolean DEBUG = false; - private static final String SIMPLE_USER_SWITCHER_GLOBAL_SETTING = - "lockscreenSimpleUserSwitcher"; - private static final int PAUSE_REFRESH_USERS_TIMEOUT_MS = 3000; - - private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; - private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000L; - - private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user"; - private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode"; - - protected final Context mContext; - protected final UserTracker mUserTracker; - protected final UserManager mUserManager; - private final ContentObserver mSettingsObserver; - private final ArrayList> mAdapters = new ArrayList<>(); - @VisibleForTesting - final GuestResumeSessionReceiver mGuestResumeSessionReceiver; - @VisibleForTesting - final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver; - private final KeyguardStateController mKeyguardStateController; - private final DeviceProvisionedController mDeviceProvisionedController; - private final DevicePolicyManager mDevicePolicyManager; - protected final Handler mHandler; - private final ActivityStarter mActivityStarter; - private final BroadcastDispatcher mBroadcastDispatcher; - private final BroadcastSender mBroadcastSender; - private final TelephonyListenerManager mTelephonyListenerManager; - private final InteractionJankMonitor mInteractionJankMonitor; - private final LatencyTracker mLatencyTracker; - private final DialogLaunchAnimator mDialogLaunchAnimator; - - private ArrayList mUsers = new ArrayList<>(); - @VisibleForTesting - AlertDialog mExitGuestDialog; - @VisibleForTesting - Dialog mAddUserDialog; - private int mLastNonGuestUser = UserHandle.USER_SYSTEM; - private boolean mSimpleUserSwitcher; - // When false, there won't be any visual affordance to add a new user from the keyguard even if - // the user is unlocked - private final MutableStateFlow mAddUsersFromLockScreen = - StateFlowKt.MutableStateFlow(false); - private boolean mUserSwitcherEnabled; - @VisibleForTesting - boolean mPauseRefreshUsers; - private int mSecondaryUser = UserHandle.USER_NULL; - private Intent mSecondaryUserServiceIntent; - private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2); - private final UiEventLogger mUiEventLogger; - private final IActivityManager mActivityManager; - private final Executor mBgExecutor; - private final Executor mUiExecutor; - private final Executor mLongRunningExecutor; - private final boolean mGuestUserAutoCreated; - private final AtomicBoolean mGuestIsResetting; - private final AtomicBoolean mGuestCreationScheduled; - private FalsingManager mFalsingManager; - @Nullable - private View mView; - private String mCreateSupervisedUserPackage; - private GlobalSettings mGlobalSettings; - private List mUserSwitchCallbacks = - Collections.synchronizedList(new ArrayList<>()); - - @Inject - public UserSwitcherControllerOldImpl( - Context context, - IActivityManager activityManager, - UserManager userManager, - UserTracker userTracker, - KeyguardStateController keyguardStateController, - DeviceProvisionedController deviceProvisionedController, - DevicePolicyManager devicePolicyManager, - @Main Handler handler, - ActivityStarter activityStarter, - BroadcastDispatcher broadcastDispatcher, - BroadcastSender broadcastSender, - UiEventLogger uiEventLogger, - FalsingManager falsingManager, - TelephonyListenerManager telephonyListenerManager, - SecureSettings secureSettings, - GlobalSettings globalSettings, - @Background Executor bgExecutor, - @LongRunning Executor longRunningExecutor, - @Main Executor uiExecutor, - InteractionJankMonitor interactionJankMonitor, - LatencyTracker latencyTracker, - DumpManager dumpManager, - DialogLaunchAnimator dialogLaunchAnimator, - GuestResumeSessionReceiver guestResumeSessionReceiver, - GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) { - mContext = context; - mActivityManager = activityManager; - mUserTracker = userTracker; - mBroadcastDispatcher = broadcastDispatcher; - mBroadcastSender = broadcastSender; - mTelephonyListenerManager = telephonyListenerManager; - mUiEventLogger = uiEventLogger; - mFalsingManager = falsingManager; - mInteractionJankMonitor = interactionJankMonitor; - mLatencyTracker = latencyTracker; - mGlobalSettings = globalSettings; - mGuestResumeSessionReceiver = guestResumeSessionReceiver; - mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver; - mBgExecutor = bgExecutor; - mLongRunningExecutor = longRunningExecutor; - mUiExecutor = uiExecutor; - mGuestResumeSessionReceiver.register(); - mGuestResetOrExitSessionReceiver.register(); - mGuestUserAutoCreated = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_guestUserAutoCreated); - mGuestIsResetting = new AtomicBoolean(); - mGuestCreationScheduled = new AtomicBoolean(); - mKeyguardStateController = keyguardStateController; - mDeviceProvisionedController = deviceProvisionedController; - mDevicePolicyManager = devicePolicyManager; - mHandler = handler; - mActivityStarter = activityStarter; - mUserManager = userManager; - mDialogLaunchAnimator = dialogLaunchAnimator; - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_ADDED); - filter.addAction(Intent.ACTION_USER_REMOVED); - filter.addAction(Intent.ACTION_USER_INFO_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); - filter.addAction(Intent.ACTION_USER_STOPPED); - filter.addAction(Intent.ACTION_USER_UNLOCKED); - mBroadcastDispatcher.registerReceiver( - mReceiver, filter, null /* executor */, - UserHandle.SYSTEM, Context.RECEIVER_EXPORTED, null /* permission */); - - mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); - - mSecondaryUserServiceIntent = new Intent(context, SystemUISecondaryUserService.class); - - filter = new IntentFilter(); - mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter, - PERMISSION_SELF, null /* scheduler */, - Context.RECEIVER_EXPORTED_UNAUDITED); - - mSettingsObserver = new ContentObserver(mHandler) { - @Override - public void onChange(boolean selfChange) { - mSimpleUserSwitcher = shouldUseSimpleUserSwitcher(); - mAddUsersFromLockScreen.setValue( - mGlobalSettings.getIntForUser( - Settings.Global.ADD_USERS_WHEN_LOCKED, - 0, - UserHandle.USER_SYSTEM) != 0); - mUserSwitcherEnabled = mGlobalSettings.getIntForUser( - Settings.Global.USER_SWITCHER_ENABLED, 0, UserHandle.USER_SYSTEM) != 0; - refreshUsers(UserHandle.USER_NULL); - }; - }; - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true, - mSettingsObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.USER_SWITCHER_ENABLED), true, - mSettingsObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ADD_USERS_WHEN_LOCKED), true, - mSettingsObserver); - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor( - Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED), - true, mSettingsObserver); - // Fetch initial values. - mSettingsObserver.onChange(false); - - keyguardStateController.addCallback(mCallback); - listenForCallState(); - - mCreateSupervisedUserPackage = mContext.getString( - com.android.internal.R.string.config_supervisedUserCreationPackage); - - dumpManager.registerDumpable(getClass().getSimpleName(), this); - - refreshUsers(UserHandle.USER_NULL); - } - - @Override - @SuppressWarnings("unchecked") - public void refreshUsers(int forcePictureLoadForId) { - if (DEBUG) Log.d(TAG, "refreshUsers(forcePictureLoadForId=" + forcePictureLoadForId + ")"); - if (forcePictureLoadForId != UserHandle.USER_NULL) { - mForcePictureLoadForUserId.put(forcePictureLoadForId, true); - } - - if (mPauseRefreshUsers) { - return; - } - - boolean forceAllUsers = mForcePictureLoadForUserId.get(UserHandle.USER_ALL); - SparseArray bitmaps = new SparseArray<>(mUsers.size()); - final int userCount = mUsers.size(); - for (int i = 0; i < userCount; i++) { - UserRecord r = mUsers.get(i); - if (r == null || r.picture == null || r.info == null || forceAllUsers - || mForcePictureLoadForUserId.get(r.info.id)) { - continue; - } - bitmaps.put(r.info.id, r.picture); - } - mForcePictureLoadForUserId.clear(); - - mBgExecutor.execute(() -> { - List infos = mUserManager.getAliveUsers(); - if (infos == null) { - return; - } - ArrayList records = new ArrayList<>(infos.size()); - int currentId = mUserTracker.getUserId(); - // Check user switchability of the foreground user since SystemUI is running in - // User 0 - boolean canSwitchUsers = mUserManager.getUserSwitchability( - UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK; - UserRecord guestRecord = null; - - for (UserInfo info : infos) { - boolean isCurrent = currentId == info.id; - if (!mUserSwitcherEnabled && !info.isPrimary()) { - continue; - } - - if (info.isEnabled()) { - 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 = LegacyUserDataHelper.createRecord( - mContext, - mUserManager, - null /* picture */, - info, - isCurrent, - canSwitchUsers); - } else if (info.supportsSwitchToByUser()) { - records.add( - LegacyUserDataHelper.createRecord( - mContext, - mUserManager, - bitmaps.get(info.id), - info, - isCurrent, - canSwitchUsers)); - } - } - } - - if (guestRecord == null) { - if (mGuestUserAutoCreated) { - // If mGuestIsResetting=true, the switch should be disabled since - // we will just use it as an indicator for "Resetting guest...". - // Otherwise, default to canSwitchUsers. - boolean isSwitchToGuestEnabled = !mGuestIsResetting.get() && canSwitchUsers; - guestRecord = LegacyUserDataHelper.createRecord( - mContext, - currentId, - UserActionModel.ENTER_GUEST_MODE, - false /* isRestricted */, - isSwitchToGuestEnabled); - records.add(guestRecord); - } else if (canCreateGuest(guestRecord != null)) { - guestRecord = LegacyUserDataHelper.createRecord( - mContext, - currentId, - UserActionModel.ENTER_GUEST_MODE, - false /* isRestricted */, - canSwitchUsers); - records.add(guestRecord); - } - } else { - records.add(guestRecord); - } - - if (canCreateUser()) { - final UserRecord userRecord = LegacyUserDataHelper.createRecord( - mContext, - currentId, - UserActionModel.ADD_USER, - createIsRestricted(), - canSwitchUsers); - records.add(userRecord); - } - - if (canCreateSupervisedUser()) { - final UserRecord userRecord = LegacyUserDataHelper.createRecord( - mContext, - currentId, - UserActionModel.ADD_SUPERVISED_USER, - createIsRestricted(), - canSwitchUsers); - records.add(userRecord); - } - - if (canManageUsers()) { - records.add(LegacyUserDataHelper.createRecord( - mContext, - KeyguardUpdateMonitor.getCurrentUser(), - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - /* isRestricted= */ false, - /* isSwitchToEnabled= */ true - )); - } - - mUiExecutor.execute(() -> { - if (records != null) { - mUsers = records; - notifyAdapters(); - } - }); - }); - } - - private boolean systemCanCreateUsers() { - return !mUserManager.hasBaseUserRestriction( - UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM); - } - - private boolean currentUserCanCreateUsers() { - UserInfo currentUser = mUserTracker.getUserInfo(); - return currentUser != null - && (currentUser.isAdmin() || mUserTracker.getUserId() == UserHandle.USER_SYSTEM) - && systemCanCreateUsers(); - } - - private boolean anyoneCanCreateUsers() { - return systemCanCreateUsers() && mAddUsersFromLockScreen.getValue(); - } - - @VisibleForTesting - boolean canCreateGuest(boolean hasExistingGuest) { - return mUserSwitcherEnabled - && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) - && !hasExistingGuest; - } - - @VisibleForTesting - boolean canCreateUser() { - return mUserSwitcherEnabled - && (currentUserCanCreateUsers() || anyoneCanCreateUsers()) - && mUserManager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY); - } - - @VisibleForTesting - boolean canManageUsers() { - UserInfo currentUser = mUserTracker.getUserInfo(); - return mUserSwitcherEnabled - && ((currentUser != null && currentUser.isAdmin()) - || mAddUsersFromLockScreen.getValue()); - } - - private boolean createIsRestricted() { - return !mAddUsersFromLockScreen.getValue(); - } - - @VisibleForTesting - boolean canCreateSupervisedUser() { - return !TextUtils.isEmpty(mCreateSupervisedUserPackage) && canCreateUser(); - } - - private void pauseRefreshUsers() { - if (!mPauseRefreshUsers) { - mHandler.postDelayed(mUnpauseRefreshUsers, PAUSE_REFRESH_USERS_TIMEOUT_MS); - mPauseRefreshUsers = true; - } - } - - private void notifyAdapters() { - for (int i = mAdapters.size() - 1; i >= 0; i--) { - BaseUserSwitcherAdapter adapter = mAdapters.get(i).get(); - if (adapter != null) { - adapter.notifyDataSetChanged(); - } else { - mAdapters.remove(i); - } - } - } - - @Override - public boolean isSimpleUserSwitcher() { - return mSimpleUserSwitcher; - } - - /** - * Returns whether the current user is a system user. - */ - @VisibleForTesting - boolean isSystemUser() { - return mUserTracker.getUserId() == UserHandle.USER_SYSTEM; - } - - @Override - public @Nullable UserRecord getCurrentUserRecord() { - for (int i = 0; i < mUsers.size(); ++i) { - UserRecord userRecord = mUsers.get(i); - if (userRecord.isCurrent) { - return userRecord; - } - } - return null; - } - - @Override - public void onUserSelected(int userId, @Nullable DialogShower dialogShower) { - UserRecord userRecord = mUsers.stream() - .filter(x -> x.resolveId() == userId) - .findFirst() - .orElse(null); - if (userRecord == null) { - return; - } - - onUserListItemClicked(userRecord, dialogShower); - } - - @Override - public Flow isAddUsersFromLockScreenEnabled() { - return mAddUsersFromLockScreen; - } - - @Override - public boolean isGuestUserAutoCreated() { - return mGuestUserAutoCreated; - } - - @Override - public boolean isGuestUserResetting() { - return mGuestIsResetting.get(); - } - - @Override - public void onUserListItemClicked(UserRecord record, DialogShower dialogShower) { - if (record.isGuest && record.info == null) { - createAndSwitchToGuestUser(dialogShower); - } else if (record.isAddUser) { - showAddUserDialog(dialogShower); - } else if (record.isAddSupervisedUser) { - startSupervisedUserActivity(); - } else if (record.isManageUsers) { - startActivity(new Intent(Settings.ACTION_USER_SETTINGS)); - } else { - onUserListItemClicked(record.info.id, record, dialogShower); - } - } - - private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) { - int currUserId = mUserTracker.getUserId(); - // If switching from guest and guest is ephemeral, then follow the flow - // of showExitGuestDialog to remove current guest, - // and switch to selected user - UserInfo currUserInfo = mUserTracker.getUserInfo(); - if (currUserId == id) { - if (record.isGuest) { - showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower); - } - return; - } - - if (currUserInfo != null && currUserInfo.isGuest()) { - showExitGuestDialog(currUserId, currUserInfo.isEphemeral(), - record.resolveId(), dialogShower); - return; - } - - if (dialogShower != null) { - // If we haven't morphed into another dialog, it means we have just switched users. - // Then, dismiss the dialog. - dialogShower.dismiss(); - } - switchToUserId(id); - } - - private void switchToUserId(int id) { - try { - if (mView != null) { - mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder - .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView) - .setTimeout(MULTI_USER_JOURNEY_TIMEOUT)); - } - mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH); - pauseRefreshUsers(); - mActivityManager.switchUser(id); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't switch user.", e); - } - } - - private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) { - int newId = UserHandle.USER_SYSTEM; - if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { - UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); - if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { - newId = info.id; - } - } - showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower); - } - - private void showExitGuestDialog( - int id, - boolean isGuestEphemeral, - int targetId, - DialogShower dialogShower) { - if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { - mExitGuestDialog.cancel(); - } - mExitGuestDialog = new ExitGuestDialog( - mContext, - id, - isGuestEphemeral, - targetId, - mKeyguardStateController.isShowing(), - mFalsingManager, - mDialogLaunchAnimator, - this::exitGuestUser); - if (dialogShower != null) { - dialogShower.showDialog(mExitGuestDialog, new DialogCuj( - InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, - INTERACTION_JANK_EXIT_GUEST_MODE_TAG)); - } else { - mExitGuestDialog.show(); - } - } - - @Override - public void createAndSwitchToGuestUser(@Nullable DialogShower dialogShower) { - createGuestAsync(guestId -> { - // guestId may be USER_NULL if we haven't reloaded the user list yet. - if (guestId != UserHandle.USER_NULL) { - mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD); - onUserListItemClicked(guestId, UserRecord.createForGuest(), dialogShower); - } - }); - } - - @Override - public void showAddUserDialog(@Nullable DialogShower dialogShower) { - if (mAddUserDialog != null && mAddUserDialog.isShowing()) { - mAddUserDialog.cancel(); - } - final UserInfo currentUser = mUserTracker.getUserInfo(); - mAddUserDialog = new AddUserDialog( - mContext, - currentUser.getUserHandle(), - mKeyguardStateController.isShowing(), - /* showEphemeralMessage= */currentUser.isGuest() && currentUser.isEphemeral(), - mFalsingManager, - mBroadcastSender, - mDialogLaunchAnimator); - if (dialogShower != null) { - dialogShower.showDialog(mAddUserDialog, - new DialogCuj( - InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, - INTERACTION_JANK_ADD_NEW_USER_TAG - )); - } else { - mAddUserDialog.show(); - } - } - - @Override - public void startSupervisedUserActivity() { - final Intent intent = new Intent() - .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) - .setPackage(mCreateSupervisedUserPackage) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - mContext.startActivity(intent); - } - - private void listenForCallState() { - mTelephonyListenerManager.addCallStateListener(mPhoneStateListener); - } - - private final TelephonyCallback.CallStateListener mPhoneStateListener = - new TelephonyCallback.CallStateListener() { - private int mCallState; - - @Override - public void onCallStateChanged(int state) { - if (mCallState == state) return; - if (DEBUG) Log.v(TAG, "Call state changed: " + state); - mCallState = state; - refreshUsers(UserHandle.USER_NULL); - } - }; - - private BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (DEBUG) { - Log.v(TAG, "Broadcast: a=" + intent.getAction() - + " user=" + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)); - } - - boolean unpauseRefreshUsers = false; - int forcePictureLoadForId = UserHandle.USER_NULL; - - if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { - if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) { - mExitGuestDialog.cancel(); - mExitGuestDialog = null; - } - - final int currentId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - final UserInfo userInfo = mUserManager.getUserInfo(currentId); - final int userCount = mUsers.size(); - for (int i = 0; i < userCount; i++) { - UserRecord record = mUsers.get(i); - if (record.info == null) continue; - boolean shouldBeCurrent = record.info.id == currentId; - if (record.isCurrent != shouldBeCurrent) { - mUsers.set(i, record.copyWithIsCurrent(shouldBeCurrent)); - } - if (shouldBeCurrent && !record.isGuest) { - mLastNonGuestUser = record.info.id; - } - if ((userInfo == null || !userInfo.isAdmin()) && record.isRestricted) { - // Immediately remove restricted records in case the AsyncTask is too slow. - mUsers.remove(i); - i--; - } - } - notifyUserSwitchCallbacks(); - notifyAdapters(); - - // Disconnect from the old secondary user's service - if (mSecondaryUser != UserHandle.USER_NULL) { - context.stopServiceAsUser(mSecondaryUserServiceIntent, - UserHandle.of(mSecondaryUser)); - mSecondaryUser = UserHandle.USER_NULL; - } - // Connect to the new secondary user's service (purely to ensure that a persistent - // SystemUI application is created for that user) - if (userInfo != null && userInfo.id != UserHandle.USER_SYSTEM) { - context.startServiceAsUser(mSecondaryUserServiceIntent, - UserHandle.of(userInfo.id)); - mSecondaryUser = userInfo.id; - } - unpauseRefreshUsers = true; - if (mGuestUserAutoCreated) { - // Guest user must be scheduled for creation AFTER switching to the target user. - // This avoids lock contention which will produce UX bugs on the keyguard - // (b/193933686). - // TODO(b/191067027): Move guest user recreation to system_server - guaranteeGuestPresent(); - } - } else if (Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) { - forcePictureLoadForId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, - UserHandle.USER_NULL); - } else if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { - // Unlocking the system user may require a refresh - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - if (userId != UserHandle.USER_SYSTEM) { - return; - } - } - refreshUsers(forcePictureLoadForId); - if (unpauseRefreshUsers) { - mUnpauseRefreshUsers.run(); - } - } - }; - - private final Runnable mUnpauseRefreshUsers = new Runnable() { - @Override - public void run() { - mHandler.removeCallbacks(this); - mPauseRefreshUsers = false; - refreshUsers(UserHandle.USER_NULL); - } - }; - - @Override - public void dump(PrintWriter pw, String[] args) { - pw.println("UserSwitcherController state:"); - pw.println(" mLastNonGuestUser=" + mLastNonGuestUser); - pw.print(" mUsers.size="); pw.println(mUsers.size()); - for (int i = 0; i < mUsers.size(); i++) { - final UserRecord u = mUsers.get(i); - pw.print(" "); pw.println(u.toString()); - } - pw.println("mSimpleUserSwitcher=" + mSimpleUserSwitcher); - pw.println("mGuestUserAutoCreated=" + mGuestUserAutoCreated); - } - - @Override - public String getCurrentUserName() { - if (mUsers.isEmpty()) return null; - UserRecord item = mUsers.stream().filter(x -> x.isCurrent).findFirst().orElse(null); - if (item == null || item.info == null) return null; - if (item.isGuest) return mContext.getString(com.android.internal.R.string.guest_name); - return item.info.name; - } - - @Override - public void onDensityOrFontScaleChanged() { - refreshUsers(UserHandle.USER_ALL); - } - - @Override - public void addAdapter(WeakReference adapter) { - mAdapters.add(adapter); - } - - @Override - public ArrayList getUsers() { - return mUsers; - } - - @Override - public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) { - UserInfo currentUser = mUserTracker.getUserInfo(); - if (currentUser.id != guestUserId) { - Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")" - + " is not current user (" + currentUser.id + ")"); - return; - } - if (!currentUser.isGuest()) { - Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")" - + " is not a guest"); - return; - } - - boolean marked = mUserManager.markGuestForDeletion(currentUser.id); - if (!marked) { - Log.w(TAG, "Couldn't mark the guest for deletion for user " + guestUserId); - return; - } - - if (targetUserId == UserHandle.USER_NULL) { - // Create a new guest in the foreground, and then immediately switch to it - createGuestAsync(newGuestId -> { - if (newGuestId == UserHandle.USER_NULL) { - Log.e(TAG, "Could not create new guest, switching back to system user"); - switchToUserId(UserHandle.USER_SYSTEM); - mUserManager.removeUser(currentUser.id); - try { - WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't remove guest because ActivityManager " - + "or WindowManager is dead"); - } - return; - } - switchToUserId(newGuestId); - mUserManager.removeUser(currentUser.id); - }); - } else { - if (mGuestUserAutoCreated) { - mGuestIsResetting.set(true); - } - switchToUserId(targetUserId); - mUserManager.removeUser(currentUser.id); - } - } - - @Override - public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId, - boolean forceRemoveGuestOnExit) { - UserInfo currentUser = mUserTracker.getUserInfo(); - if (currentUser.id != guestUserId) { - Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")" - + " is not current user (" + currentUser.id + ")"); - return; - } - if (!currentUser.isGuest()) { - Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")" - + " is not a guest"); - return; - } - - int newUserId = UserHandle.USER_SYSTEM; - if (targetUserId == UserHandle.USER_NULL) { - // when target user is not specified switch to last non guest user - if (mLastNonGuestUser != UserHandle.USER_SYSTEM) { - UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser); - if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) { - newUserId = info.id; - } - } - } else { - newUserId = targetUserId; - } - - if (currentUser.isEphemeral() || forceRemoveGuestOnExit) { - mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE); - removeGuestUser(currentUser.id, newUserId); - } else { - mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH); - switchToUserId(newUserId); - } - } - - private void scheduleGuestCreation() { - if (!mGuestCreationScheduled.compareAndSet(false, true)) { - return; - } - - mLongRunningExecutor.execute(() -> { - int newGuestId = createGuest(); - mGuestCreationScheduled.set(false); - mGuestIsResetting.set(false); - if (newGuestId == UserHandle.USER_NULL) { - Log.w(TAG, "Could not create new guest while exiting existing guest"); - // Refresh users so that we still display "Guest" if - // config_guestUserAutoCreated=true - refreshUsers(UserHandle.USER_NULL); - } - }); - - } - - @Override - public void schedulePostBootGuestCreation() { - if (isDeviceAllowedToAddGuest()) { - guaranteeGuestPresent(); - } else { - mDeviceProvisionedController.addCallback(mGuaranteeGuestPresentAfterProvisioned); - } - } - - private boolean isDeviceAllowedToAddGuest() { - return mDeviceProvisionedController.isDeviceProvisioned() - && !mDevicePolicyManager.isDeviceManaged(); - } - - /** - * If there is no guest on the device, schedule creation of a new guest user in the background. - */ - private void guaranteeGuestPresent() { - if (isDeviceAllowedToAddGuest() && mUserManager.findCurrentGuestUser() == null) { - scheduleGuestCreation(); - } - } - - private void createGuestAsync(Consumer callback) { - final Dialog guestCreationProgressDialog = - new UserCreatingDialog(mContext, /* isGuest= */true); - guestCreationProgressDialog.show(); - - // userManager.createGuest will block the thread so post is needed for the dialog to show - mBgExecutor.execute(() -> { - final int guestId = createGuest(); - mUiExecutor.execute(() -> { - guestCreationProgressDialog.dismiss(); - if (guestId == UserHandle.USER_NULL) { - Toast.makeText(mContext, - com.android.settingslib.R.string.add_guest_failed, - Toast.LENGTH_SHORT).show(); - } - callback.accept(guestId); - }); - }); - } - - /** - * Creates a guest user and return its multi-user user ID. - * - * This method does not check if a guest already exists before it makes a call to - * {@link UserManager} to create a new one. - * - * @return The multi-user user ID of the newly created guest user, or - * {@link UserHandle#USER_NULL} if the guest couldn't be created. - */ - private @UserIdInt int createGuest() { - UserInfo guest; - try { - guest = mUserManager.createGuest(mContext); - } catch (UserManager.UserOperationException e) { - Log.e(TAG, "Couldn't create guest user", e); - return UserHandle.USER_NULL; - } - if (guest == null) { - Log.e(TAG, "Couldn't create guest, most likely because there already exists one"); - return UserHandle.USER_NULL; - } - return guest.id; - } - - @Override - public void init(View view) { - mView = view; - } - - @Override - public boolean isKeyguardShowing() { - return mKeyguardStateController.isShowing(); - } - - private boolean shouldUseSimpleUserSwitcher() { - int defaultSimpleUserSwitcher = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_expandLockScreenUserSwitcher) ? 1 : 0; - return mGlobalSettings.getIntForUser(SIMPLE_USER_SWITCHER_GLOBAL_SETTING, - defaultSimpleUserSwitcher, UserHandle.USER_SYSTEM) != 0; - } - - @Override - public void startActivity(Intent intent) { - mActivityStarter.startActivity(intent, /* dismissShade= */ true); - } - - @Override - public void addUserSwitchCallback(UserSwitchCallback callback) { - mUserSwitchCallbacks.add(callback); - } - - @Override - public void removeUserSwitchCallback(UserSwitchCallback callback) { - mUserSwitchCallbacks.remove(callback); - } - - /** - * Notify user switch callbacks that user has switched. - */ - private void notifyUserSwitchCallbacks() { - List temp; - synchronized (mUserSwitchCallbacks) { - temp = new ArrayList<>(mUserSwitchCallbacks); - } - for (UserSwitchCallback callback : temp) { - callback.onUserSwitched(); - } - } - - private final KeyguardStateController.Callback mCallback = - new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - - // When Keyguard is going away, we don't need to update our items immediately - // which - // helps making the transition faster. - if (!mKeyguardStateController.isShowing()) { - mHandler.post(UserSwitcherControllerOldImpl.this::notifyAdapters); - } else { - notifyAdapters(); - } - } - }; - - private final DeviceProvisionedController.DeviceProvisionedListener - mGuaranteeGuestPresentAfterProvisioned = - new DeviceProvisionedController.DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - if (isDeviceAllowedToAddGuest()) { - mBgExecutor.execute( - () -> mDeviceProvisionedController.removeCallback( - mGuaranteeGuestPresentAfterProvisioned)); - guaranteeGuestPresent(); - } - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index b1b45b51d8e4c..1b7353923adaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -58,8 +58,6 @@ import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityControllerImpl; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.statusbar.policy.UserSwitcherControllerImpl; import com.android.systemui.statusbar.policy.WalletController; import com.android.systemui.statusbar.policy.WalletControllerImpl; import com.android.systemui.statusbar.policy.ZenModeController; @@ -198,8 +196,4 @@ public interface StatusBarPolicyModule { static DataSaverController provideDataSaverController(NetworkController networkController) { return networkController.getDataSaverController(); } - - /** Binds {@link UserSwitcherController} to its implementation. */ - @Binds - UserSwitcherController bindUserSwitcherController(UserSwitcherControllerImpl impl); } 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 ffaf524bb0d11..ed53de7dbee77 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 @@ -19,31 +19,18 @@ package com.android.systemui.user.data.repository import android.content.Context import android.content.pm.UserInfo -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.annotation.VisibleForTesting -import androidx.appcompat.content.res.AppCompatResources -import com.android.internal.util.UserIcons -import com.android.systemui.R import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow -import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.user.data.model.UserSwitcherSettingsModel -import com.android.systemui.user.data.source.UserRecord -import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper -import com.android.systemui.user.shared.model.UserActionModel -import com.android.systemui.user.shared.model.UserModel import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow import java.util.concurrent.atomic.AtomicBoolean @@ -55,7 +42,6 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -72,15 +58,6 @@ import kotlinx.coroutines.withContext * upstream changes. */ interface UserRepository { - /** List of all users on the device. */ - val users: Flow> - - /** The currently-selected user. */ - val selectedUser: Flow - - /** List of available user-related actions. */ - val actions: Flow> - /** User switcher related settings. */ val userSwitcherSettings: Flow @@ -93,9 +70,6 @@ interface UserRepository { /** User ID of the last non-guest selected user. */ val lastSelectedNonGuestUserId: Int - /** Whether actions are available even when locked. */ - val isActionableWhenLocked: Flow - /** Whether the device is configured to always have a guest user available. */ val isGuestUserAutoCreated: Boolean @@ -125,18 +99,13 @@ class UserRepositoryImpl constructor( @Application private val appContext: Context, private val manager: UserManager, - private val controller: UserSwitcherController, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, @Background private val backgroundDispatcher: CoroutineDispatcher, private val globalSettings: GlobalSettings, private val tracker: UserTracker, - private val featureFlags: FeatureFlags, ) : UserRepository { - private val isNewImpl: Boolean - get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER) - private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() }) override val userSwitcherSettings: Flow = _userSwitcherSettings.asStateFlow().filterNotNull() @@ -150,58 +119,11 @@ constructor( override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM private set - private val userRecords: Flow> = conflatedCallbackFlow { - fun send() { - trySendWithFailureLogging( - controller.users, - TAG, - ) - } - - val callback = UserSwitcherController.UserSwitchCallback { send() } - - controller.addUserSwitchCallback(callback) - send() - - awaitClose { controller.removeUserSwitchCallback(callback) } - } - - override val users: Flow> = - userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } } - - override val selectedUser: Flow = - users.map { users -> users.first { user -> user.isSelected } } - - override val actions: Flow> = - userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } } - - override val isActionableWhenLocked: Flow = - if (isNewImpl) { - emptyFlow() - } else { - controller.isAddUsersFromLockScreenEnabled - } - override val isGuestUserAutoCreated: Boolean = - if (isNewImpl) { - appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated) - } else { - controller.isGuestUserAutoCreated - } + appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated) private var _isGuestUserResetting: Boolean = false - override var isGuestUserResetting: Boolean = - if (isNewImpl) { - _isGuestUserResetting - } else { - controller.isGuestUserResetting - } - set(value) = - if (isNewImpl) { - _isGuestUserResetting = value - } else { - error("Not supported in the old implementation!") - } + override var isGuestUserResetting: Boolean = _isGuestUserResetting override val isGuestUserCreationScheduled = AtomicBoolean() @@ -210,10 +132,8 @@ constructor( override var isRefreshUsersPaused: Boolean = false init { - if (isNewImpl) { - observeSelectedUser() - observeUserSettings() - } + observeSelectedUser() + observeUserSettings() } override fun refreshUsers() { @@ -327,64 +247,6 @@ constructor( } } - private fun UserRecord.isUser(): Boolean { - return when { - isAddUser -> false - isAddSupervisedUser -> false - isManageUsers -> false - isGuest -> info != null - else -> true - } - } - - private fun UserRecord.isNotUser(): Boolean { - return !isUser() - } - - private fun UserRecord.toUserModel(): UserModel { - return UserModel( - id = resolveId(), - name = getUserName(this), - image = getUserImage(this), - isSelected = isCurrent, - isSelectable = isSwitchToEnabled || isGuest, - isGuest = isGuest, - ) - } - - private fun UserRecord.toActionModel(): UserActionModel { - return when { - isAddUser -> UserActionModel.ADD_USER - isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER - isGuest -> UserActionModel.ENTER_GUEST_MODE - isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT - else -> error("Don't know how to convert to UserActionModel: $this") - } - } - - private fun getUserName(record: UserRecord): Text { - val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record) - return if (resourceId != null) { - Text.Resource(resourceId) - } else { - Text.Loaded(checkNotNull(record.info).name) - } - } - - private fun getUserImage(record: UserRecord): Drawable { - if (record.isGuest) { - return checkNotNull( - AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle) - ) - } - - val userId = checkNotNull(record.info?.id) - return manager.getUserIcon(userId)?.let { userSelectedIcon -> - BitmapDrawable(userSelectedIcon) - } - ?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false) - } - companion object { private const val TAG = "UserRepository" @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher" diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt index dda78aad54c6e..6b81bf2cfb082 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt @@ -39,12 +39,9 @@ import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController -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 @@ -64,8 +61,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -82,10 +77,8 @@ class UserInteractor constructor( @Application private val applicationContext: Context, private val repository: UserRepository, - private val controller: UserSwitcherController, private val activityStarter: ActivityStarter, private val keyguardInteractor: KeyguardInteractor, - private val featureFlags: FeatureFlags, private val manager: UserManager, @Application private val applicationScope: CoroutineScope, telephonyInteractor: TelephonyInteractor, @@ -107,9 +100,6 @@ constructor( fun onUserStateChanged() } - private val isNewImpl: Boolean - get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER) - private val supervisedUserPackageName: String? get() = applicationContext.getString( @@ -122,181 +112,146 @@ constructor( /** List of current on-device users to select from. */ val users: Flow> get() = - if (isNewImpl) { - combine( - repository.userInfos, - repository.selectedUserInfo, - repository.userSwitcherSettings, - ) { userInfos, selectedUserInfo, settings -> - toUserModels( - userInfos = userInfos, - selectedUserId = selectedUserInfo.id, - isUserSwitcherEnabled = settings.isUserSwitcherEnabled, - ) - } - } else { - repository.users + combine( + repository.userInfos, + repository.selectedUserInfo, + repository.userSwitcherSettings, + ) { userInfos, selectedUserInfo, settings -> + toUserModels( + userInfos = userInfos, + selectedUserId = selectedUserInfo.id, + isUserSwitcherEnabled = settings.isUserSwitcherEnabled, + ) } /** The currently-selected user. */ val selectedUser: Flow get() = - if (isNewImpl) { - combine( - repository.selectedUserInfo, - repository.userSwitcherSettings, - ) { selectedUserInfo, settings -> - val selectedUserId = selectedUserInfo.id - checkNotNull( - toUserModel( - userInfo = selectedUserInfo, - selectedUserId = selectedUserId, - canSwitchUsers = canSwitchUsers(selectedUserId), - isUserSwitcherEnabled = settings.isUserSwitcherEnabled, - ) + combine( + repository.selectedUserInfo, + repository.userSwitcherSettings, + ) { selectedUserInfo, settings -> + val selectedUserId = selectedUserInfo.id + checkNotNull( + toUserModel( + userInfo = selectedUserInfo, + selectedUserId = selectedUserId, + canSwitchUsers = canSwitchUsers(selectedUserId), + isUserSwitcherEnabled = settings.isUserSwitcherEnabled, ) - } - } else { - repository.selectedUser + ) } /** List of user-switcher related actions that are available. */ val actions: Flow> get() = - if (isNewImpl) { - combine( - repository.selectedUserInfo, - repository.userInfos, - repository.userSwitcherSettings, - keyguardInteractor.isKeyguardShowing, - ) { _, userInfos, settings, isDeviceLocked -> - buildList { - val hasGuestUser = userInfos.any { it.isGuest } - if ( - !hasGuestUser && - (guestUserInteractor.isGuestUserAutoCreated || - UserActionsUtil.canCreateGuest( - manager, - repository, - settings.isUserSwitcherEnabled, - settings.isAddUsersFromLockscreen, - )) - ) { - add(UserActionModel.ENTER_GUEST_MODE) - } - - if (!isDeviceLocked || settings.isAddUsersFromLockscreen) { - // The device is locked and our setting to allow actions that add users - // from the lock-screen is not enabled. The guest action from above is - // always allowed, even when the device is locked, but the various "add - // user" actions below are not. We can finish building the list here. - - val canCreateUsers = - UserActionsUtil.canCreateUser( + combine( + repository.selectedUserInfo, + repository.userInfos, + repository.userSwitcherSettings, + keyguardInteractor.isKeyguardShowing, + ) { _, userInfos, settings, isDeviceLocked -> + buildList { + val hasGuestUser = userInfos.any { it.isGuest } + if ( + !hasGuestUser && + (guestUserInteractor.isGuestUserAutoCreated || + UserActionsUtil.canCreateGuest( manager, repository, settings.isUserSwitcherEnabled, settings.isAddUsersFromLockscreen, - ) + )) + ) { + add(UserActionModel.ENTER_GUEST_MODE) + } - if (canCreateUsers) { - add(UserActionModel.ADD_USER) - } + if (!isDeviceLocked || settings.isAddUsersFromLockscreen) { + // The device is locked and our setting to allow actions that add users + // from the lock-screen is not enabled. The guest action from above is + // always allowed, even when the device is locked, but the various "add + // user" actions below are not. We can finish building the list here. - if ( - UserActionsUtil.canCreateSupervisedUser( - manager, - repository, - settings.isUserSwitcherEnabled, - settings.isAddUsersFromLockscreen, - supervisedUserPackageName, - ) - ) { - add(UserActionModel.ADD_SUPERVISED_USER) - } - } - - if ( - UserActionsUtil.canManageUsers( + val canCreateUsers = + UserActionsUtil.canCreateUser( + manager, repository, settings.isUserSwitcherEnabled, settings.isAddUsersFromLockscreen, ) - ) { - add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) + + if (canCreateUsers) { + add(UserActionModel.ADD_USER) } + + if ( + UserActionsUtil.canCreateSupervisedUser( + manager, + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + supervisedUserPackageName, + ) + ) { + add(UserActionModel.ADD_SUPERVISED_USER) + } + } + + if ( + UserActionsUtil.canManageUsers( + repository, + settings.isUserSwitcherEnabled, + settings.isAddUsersFromLockscreen, + ) + ) { + add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) } } - } else { - combine( - repository.isActionableWhenLocked, - keyguardInteractor.isKeyguardShowing, - ) { isActionableWhenLocked, isLocked -> - isActionableWhenLocked || !isLocked - } - .flatMapLatest { isActionable -> - if (isActionable) { - repository.actions - } else { - // If not actionable it means that we're not allowed to show actions - // when - // locked and we are locked. Therefore, we should show no actions. - flowOf(emptyList()) - } - } } val userRecords: StateFlow> = - if (isNewImpl) { - combine( - repository.userInfos, - repository.selectedUserInfo, - actions, - repository.userSwitcherSettings, - ) { userInfos, selectedUserInfo, actionModels, settings -> - ArrayList( - userInfos.map { + combine( + repository.userInfos, + repository.selectedUserInfo, + actions, + repository.userSwitcherSettings, + ) { userInfos, selectedUserInfo, actionModels, settings -> + ArrayList( + userInfos.map { + toRecord( + userInfo = it, + selectedUserId = selectedUserInfo.id, + ) + } + + actionModels.map { toRecord( - userInfo = it, + action = it, selectedUserId = selectedUserInfo.id, + isRestricted = + it != UserActionModel.ENTER_GUEST_MODE && + it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT && + !settings.isAddUsersFromLockscreen, ) - } + - actionModels.map { - toRecord( - action = it, - selectedUserId = selectedUserInfo.id, - isRestricted = - it != UserActionModel.ENTER_GUEST_MODE && - it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT && - !settings.isAddUsersFromLockscreen, - ) - } - ) - } - .onEach { notifyCallbacks() } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = ArrayList(), + } ) - } else { - MutableStateFlow(ArrayList()) - } + } + .onEach { notifyCallbacks() } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = ArrayList(), + ) val selectedUserRecord: StateFlow = - if (isNewImpl) { - repository.selectedUserInfo - .map { selectedUserInfo -> - toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id) - } - .stateIn( - scope = applicationScope, - started = SharingStarted.Eagerly, - initialValue = null, - ) - } else { - MutableStateFlow(null) - } + repository.selectedUserInfo + .map { selectedUserInfo -> + toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id) + } + .stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = null, + ) /** Whether the device is configured to always have a guest user available. */ val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated @@ -311,44 +266,37 @@ constructor( val dialogDismissRequests: Flow = _dialogDismissRequests.asStateFlow() val isSimpleUserSwitcher: Boolean - get() = - if (isNewImpl) { - repository.isSimpleUserSwitcher() - } else { - error("Not supported in the old implementation!") - } + get() = repository.isSimpleUserSwitcher() init { - if (isNewImpl) { - refreshUsersScheduler.refreshIfNotPaused() - telephonyInteractor.callState - .distinctUntilChanged() - .onEach { refreshUsersScheduler.refreshIfNotPaused() } - .launchIn(applicationScope) + refreshUsersScheduler.refreshIfNotPaused() + telephonyInteractor.callState + .distinctUntilChanged() + .onEach { refreshUsersScheduler.refreshIfNotPaused() } + .launchIn(applicationScope) - combine( - broadcastDispatcher.broadcastFlow( - filter = - IntentFilter().apply { - addAction(Intent.ACTION_USER_ADDED) - addAction(Intent.ACTION_USER_REMOVED) - addAction(Intent.ACTION_USER_INFO_CHANGED) - addAction(Intent.ACTION_USER_SWITCHED) - addAction(Intent.ACTION_USER_STOPPED) - addAction(Intent.ACTION_USER_UNLOCKED) - }, - user = UserHandle.SYSTEM, - map = { intent, _ -> intent }, - ), - repository.selectedUserInfo.pairwise(null), - ) { intent, selectedUserChange -> - Pair(intent, selectedUserChange.previousValue) - } - .onEach { (intent, previousSelectedUser) -> - onBroadcastReceived(intent, previousSelectedUser) - } - .launchIn(applicationScope) - } + combine( + broadcastDispatcher.broadcastFlow( + filter = + IntentFilter().apply { + addAction(Intent.ACTION_USER_ADDED) + addAction(Intent.ACTION_USER_REMOVED) + addAction(Intent.ACTION_USER_INFO_CHANGED) + addAction(Intent.ACTION_USER_SWITCHED) + addAction(Intent.ACTION_USER_STOPPED) + addAction(Intent.ACTION_USER_UNLOCKED) + }, + user = UserHandle.SYSTEM, + map = { intent, _ -> intent }, + ), + repository.selectedUserInfo.pairwise(null), + ) { intent, selectedUserChange -> + Pair(intent, selectedUserChange.previousValue) + } + .onEach { (intent, previousSelectedUser) -> + onBroadcastReceived(intent, previousSelectedUser) + } + .launchIn(applicationScope) } fun addCallback(callback: UserCallback) { @@ -414,48 +362,43 @@ constructor( newlySelectedUserId: Int, dialogShower: UserSwitchDialogController.DialogShower? = null, ) { - if (isNewImpl) { - val currentlySelectedUserInfo = repository.getSelectedUserInfo() - if ( - newlySelectedUserId == currentlySelectedUserInfo.id && - currentlySelectedUserInfo.isGuest - ) { - // Here when clicking on the currently-selected guest user to leave guest mode - // and return to the previously-selected non-guest user. - showDialog( - ShowDialogRequestModel.ShowExitGuestDialog( - guestUserId = currentlySelectedUserInfo.id, - targetUserId = repository.lastSelectedNonGuestUserId, - isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, - isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), - onExitGuestUser = this::exitGuestUser, - dialogShower = dialogShower, - ) + val currentlySelectedUserInfo = repository.getSelectedUserInfo() + if ( + newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest + ) { + // Here when clicking on the currently-selected guest user to leave guest mode + // and return to the previously-selected non-guest user. + showDialog( + ShowDialogRequestModel.ShowExitGuestDialog( + guestUserId = currentlySelectedUserInfo.id, + targetUserId = repository.lastSelectedNonGuestUserId, + isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, + isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), + onExitGuestUser = this::exitGuestUser, + dialogShower = dialogShower, ) - return - } - - if (currentlySelectedUserInfo.isGuest) { - // Here when switching from guest to a non-guest user. - showDialog( - ShowDialogRequestModel.ShowExitGuestDialog( - guestUserId = currentlySelectedUserInfo.id, - targetUserId = newlySelectedUserId, - isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, - isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), - onExitGuestUser = this::exitGuestUser, - dialogShower = dialogShower, - ) - ) - return - } - - dialogShower?.dismiss() - - switchUser(newlySelectedUserId) - } else { - controller.onUserSelected(newlySelectedUserId, dialogShower) + ) + return } + + if (currentlySelectedUserInfo.isGuest) { + // Here when switching from guest to a non-guest user. + showDialog( + ShowDialogRequestModel.ShowExitGuestDialog( + guestUserId = currentlySelectedUserInfo.id, + targetUserId = newlySelectedUserId, + isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, + isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), + onExitGuestUser = this::exitGuestUser, + dialogShower = dialogShower, + ) + ) + return + } + + dialogShower?.dismiss() + + switchUser(newlySelectedUserId) } /** Executes the given action. */ @@ -463,51 +406,38 @@ constructor( action: UserActionModel, dialogShower: UserSwitchDialogController.DialogShower? = null, ) { - if (isNewImpl) { - when (action) { - UserActionModel.ENTER_GUEST_MODE -> - guestUserInteractor.createAndSwitchTo( - this::showDialog, - this::dismissDialog, - ) { userId -> - selectUser(userId, dialogShower) - } - UserActionModel.ADD_USER -> { - val currentUser = repository.getSelectedUserInfo() - showDialog( - ShowDialogRequestModel.ShowAddUserDialog( - userHandle = currentUser.userHandle, - isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), - showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral, - dialogShower = dialogShower, - ) - ) + when (action) { + UserActionModel.ENTER_GUEST_MODE -> + guestUserInteractor.createAndSwitchTo( + this::showDialog, + this::dismissDialog, + ) { userId -> + selectUser(userId, dialogShower) } - UserActionModel.ADD_SUPERVISED_USER -> - activityStarter.startActivity( - Intent() - .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) - .setPackage(supervisedUserPackageName) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - /* dismissShade= */ true, - ) - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT -> - activityStarter.startActivity( - Intent(Settings.ACTION_USER_SETTINGS), - /* dismissShade= */ true, - ) - } - } else { - when (action) { - UserActionModel.ENTER_GUEST_MODE -> controller.createAndSwitchToGuestUser(null) - UserActionModel.ADD_USER -> controller.showAddUserDialog(null) - UserActionModel.ADD_SUPERVISED_USER -> controller.startSupervisedUserActivity() - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT -> - activityStarter.startActivity( - Intent(Settings.ACTION_USER_SETTINGS), - /* dismissShade= */ false, + UserActionModel.ADD_USER -> { + val currentUser = repository.getSelectedUserInfo() + showDialog( + ShowDialogRequestModel.ShowAddUserDialog( + userHandle = currentUser.userHandle, + isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), + showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral, + dialogShower = dialogShower, ) + ) } + UserActionModel.ADD_SUPERVISED_USER -> + activityStarter.startActivity( + Intent() + .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) + .setPackage(supervisedUserPackageName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), + /* dismissShade= */ true, + ) + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT -> + activityStarter.startActivity( + Intent(Settings.ACTION_USER_SETTINGS), + /* dismissShade= */ true, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt index e9217209530be..58a4473186b39 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt @@ -27,15 +27,12 @@ import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.FalsingManager import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.domain.model.ShowDialogRequestModel import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.launch @@ -50,16 +47,11 @@ constructor( private val broadcastSender: Lazy, private val dialogLaunchAnimator: Lazy, private val interactor: Lazy, - private val featureFlags: Lazy, ) : CoreStartable { private var currentDialog: Dialog? = null override fun start() { - if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) { - return - } - startHandlingDialogShowRequests() startHandlingDialogDismissRequests() } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt index d857e85bac53a..0910ea36b7fff 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt @@ -20,8 +20,6 @@ package com.android.systemui.user.ui.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.android.systemui.common.ui.drawable.CircularDrawable -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.UserInteractor @@ -41,12 +39,8 @@ private constructor( private val userInteractor: UserInteractor, private val guestUserInteractor: GuestUserInteractor, private val powerInteractor: PowerInteractor, - private val featureFlags: FeatureFlags, ) : ViewModel() { - private val isNewImpl: Boolean - get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER) - /** On-device users. */ val users: Flow> = userInteractor.users.map { models -> models.map { user -> toViewModel(user) } } @@ -216,7 +210,6 @@ private constructor( private val userInteractor: UserInteractor, private val guestUserInteractor: GuestUserInteractor, private val powerInteractor: PowerInteractor, - private val featureFlags: FeatureFlags, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { @Suppress("UNCHECKED_CAST") @@ -224,7 +217,6 @@ private constructor( userInteractor = userInteractor, guestUserInteractor = guestUserInteractor, powerInteractor = powerInteractor, - featureFlags = featureFlags, ) as T } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index 3131f60893c74..08a90b79089e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -42,27 +42,21 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.mock import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest class UserDetailViewAdapterTest : SysuiTestCase() { - @Mock - private lateinit var mUserSwitcherController: UserSwitcherController - @Mock - private lateinit var mParent: ViewGroup - @Mock - private lateinit var mUserDetailItemView: UserDetailItemView - @Mock - private lateinit var mOtherView: View - @Mock - private lateinit var mInflatedUserDetailItemView: UserDetailItemView - @Mock - private lateinit var mLayoutInflater: LayoutInflater + @Mock private lateinit var mUserSwitcherController: UserSwitcherController + @Mock private lateinit var mParent: ViewGroup + @Mock private lateinit var mUserDetailItemView: UserDetailItemView + @Mock private lateinit var mOtherView: View + @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView + @Mock private lateinit var mLayoutInflater: LayoutInflater private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake() private lateinit var adapter: UserDetailView.Adapter private lateinit var uiEventLogger: UiEventLoggerFake @@ -77,10 +71,13 @@ class UserDetailViewAdapterTest : SysuiTestCase() { `when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean())) .thenReturn(mInflatedUserDetailItemView) `when`(mParent.context).thenReturn(mContext) - adapter = UserDetailView.Adapter( - mContext, mUserSwitcherController, uiEventLogger, - falsingManagerFake - ) + adapter = + UserDetailView.Adapter( + mContext, + mUserSwitcherController, + uiEventLogger, + falsingManagerFake + ) mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt index f3046477f4d16..0a3da0b5b029d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt @@ -237,7 +237,7 @@ class BaseUserSwitcherAdapterTest : SysuiTestCase() { fun refresh() { underTest.refresh() - verify(controller).refreshUsers(UserHandle.USER_NULL) + verify(controller).refreshUsers() } private fun createUserRecord( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt deleted file mode 100644 index 169f4fb2715be..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerOldImplTest.kt +++ /dev/null @@ -1,727 +0,0 @@ -/* - * Copyright (C) 2021 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.statusbar.policy - -import android.app.IActivityManager -import android.app.NotificationManager -import android.app.admin.DevicePolicyManager -import android.content.BroadcastReceiver -import android.content.Context -import android.content.DialogInterface -import android.content.Intent -import android.content.pm.UserInfo -import android.graphics.Bitmap -import android.hardware.face.FaceManager -import android.hardware.fingerprint.FingerprintManager -import android.os.Handler -import android.os.UserHandle -import android.os.UserManager -import android.provider.Settings -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.ThreadedRenderer -import androidx.test.filters.SmallTest -import com.android.internal.jank.InteractionJankMonitor -import com.android.internal.logging.testing.UiEventLoggerFake -import com.android.internal.util.LatencyTracker -import com.android.internal.util.UserIcons -import com.android.systemui.GuestResetOrExitSessionReceiver -import com.android.systemui.GuestResumeSessionReceiver -import com.android.systemui.GuestSessionNotification -import com.android.systemui.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.DialogCuj -import com.android.systemui.animation.DialogLaunchAnimator -import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.broadcast.BroadcastSender -import com.android.systemui.dump.DumpManager -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.plugins.FalsingManager -import com.android.systemui.qs.QSUserSwitcherEvent -import com.android.systemui.qs.user.UserSwitchDialogController -import com.android.systemui.settings.UserTracker -import com.android.systemui.shade.NotificationShadeWindowView -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.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.kotlinArgumentCaptor -import com.android.systemui.util.mockito.nullable -import com.android.systemui.util.settings.GlobalSettings -import com.android.systemui.util.settings.SecureSettings -import com.android.systemui.util.time.FakeSystemClock -import com.google.common.truth.Truth -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.Mockito.doNothing -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.eq -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations - -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -@SmallTest -class UserSwitcherControllerOldImplTest : SysuiTestCase() { - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var activityManager: IActivityManager - @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController - @Mock private lateinit var devicePolicyManager: DevicePolicyManager - @Mock private lateinit var handler: Handler - @Mock private lateinit var userTracker: UserTracker - @Mock private lateinit var userManager: UserManager - @Mock private lateinit var activityStarter: ActivityStarter - @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var broadcastSender: BroadcastSender - @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager - @Mock private lateinit var secureSettings: SecureSettings - @Mock private lateinit var falsingManager: FalsingManager - @Mock private lateinit var dumpManager: DumpManager - @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock private lateinit var latencyTracker: LatencyTracker - @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower - @Mock private lateinit var notificationShadeWindowView: NotificationShadeWindowView - @Mock private lateinit var threadedRenderer: ThreadedRenderer - @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator - @Mock private lateinit var globalSettings: GlobalSettings - @Mock private lateinit var guestSessionNotification: GuestSessionNotification - @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver - private lateinit var resetSessionDialogFactory: - GuestResumeSessionReceiver.ResetSessionDialog.Factory - private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver - private lateinit var testableLooper: TestableLooper - private lateinit var bgExecutor: FakeExecutor - private lateinit var longRunningExecutor: FakeExecutor - private lateinit var uiExecutor: FakeExecutor - private lateinit var uiEventLogger: UiEventLoggerFake - private lateinit var userSwitcherController: UserSwitcherControllerOldImpl - private lateinit var picture: Bitmap - private val ownerId = UserHandle.USER_SYSTEM - private val ownerInfo = UserInfo(ownerId, "Owner", null, - UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or - UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_ADMIN, - UserManager.USER_TYPE_FULL_SYSTEM) - private val guestId = 1234 - private val guestInfo = UserInfo(guestId, "Guest", null, - UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST) - private val secondaryUser = - UserInfo(10, "Secondary", null, 0, UserManager.USER_TYPE_FULL_SECONDARY) - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - testableLooper = TestableLooper.get(this) - bgExecutor = FakeExecutor(FakeSystemClock()) - longRunningExecutor = FakeExecutor(FakeSystemClock()) - uiExecutor = FakeExecutor(FakeSystemClock()) - uiEventLogger = UiEventLoggerFake() - - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_guestUserAutoCreated, false) - - mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java)) - mContext.addMockSystemService(Context.NOTIFICATION_SERVICE, - mock(NotificationManager::class.java)) - mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, - mock(FingerprintManager::class.java)) - - resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory { - override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog { - return GuestResumeSessionReceiver.ResetSessionDialog( - mContext, - mock(UserSwitcherController::class.java), - uiEventLogger, - userId - ) - } - } - - guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker, - secureSettings, - broadcastDispatcher, - guestSessionNotification, - resetSessionDialogFactory) - - `when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY))) - .thenReturn(true) - `when`(notificationShadeWindowView.context).thenReturn(context) - - // Since userSwitcherController involves InteractionJankMonitor. - // Let's fulfill the dependencies. - val mockedContext = mock(Context::class.java) - doReturn(mockedContext).`when`(notificationShadeWindowView).context - doReturn(true).`when`(notificationShadeWindowView).isAttachedToWindow - doNothing().`when`(threadedRenderer).addObserver(any()) - doNothing().`when`(threadedRenderer).removeObserver(any()) - doReturn(threadedRenderer).`when`(notificationShadeWindowView).threadedRenderer - - picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user)) - - // Create defaults for the current user - `when`(userTracker.userId).thenReturn(ownerId) - `when`(userTracker.userInfo).thenReturn(ownerInfo) - - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.ADD_USERS_WHEN_LOCKED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(0) - - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(1) - - setupController() - } - - private fun setupController() { - userSwitcherController = - UserSwitcherControllerOldImpl( - mContext, - activityManager, - userManager, - userTracker, - keyguardStateController, - deviceProvisionedController, - devicePolicyManager, - handler, - activityStarter, - broadcastDispatcher, - broadcastSender, - uiEventLogger, - falsingManager, - telephonyListenerManager, - secureSettings, - globalSettings, - bgExecutor, - longRunningExecutor, - uiExecutor, - interactionJankMonitor, - latencyTracker, - dumpManager, - dialogLaunchAnimator, - guestResumeSessionReceiver, - guestResetOrExitSessionReceiver - ) - userSwitcherController.init(notificationShadeWindowView) - } - - @Test - fun testSwitchUser_parentDialogDismissed() { - val otherUserRecord = UserRecord( - secondaryUser, - picture, - false /* guest */, - false /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(ownerId) - `when`(userTracker.userInfo).thenReturn(ownerInfo) - - userSwitcherController.onUserListItemClicked(otherUserRecord, dialogShower) - testableLooper.processAllMessages() - - verify(dialogShower).dismiss() - } - - @Test - fun testAddGuest_okButtonPressed() { - val emptyGuestUserRecord = - UserRecord( - null, - null, - true /* guest */, - false /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(ownerId) - `when`(userTracker.userInfo).thenReturn(ownerInfo) - - `when`(userManager.createGuest(any())).thenReturn(guestInfo) - - userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null) - bgExecutor.runAllReady() - uiExecutor.runAllReady() - testableLooper.processAllMessages() - verify(interactionJankMonitor).begin(any()) - verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH) - verify(activityManager).switchUser(guestInfo.id) - assertEquals(1, uiEventLogger.numLogs()) - assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0)) - } - - @Test - fun testAddGuest_parentDialogDismissed() { - val emptyGuestUserRecord = - UserRecord( - null, - null, - true /* guest */, - false /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(ownerId) - `when`(userTracker.userInfo).thenReturn(ownerInfo) - - `when`(userManager.createGuest(any())).thenReturn(guestInfo) - - userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower) - bgExecutor.runAllReady() - uiExecutor.runAllReady() - testableLooper.processAllMessages() - verify(dialogShower).dismiss() - } - - @Test - fun testRemoveGuest_removeButtonPressed_isLogged() { - val currentGuestUserRecord = - UserRecord( - guestInfo, - picture, - true /* guest */, - true /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(guestInfo.id) - `when`(userTracker.userInfo).thenReturn(guestInfo) - - userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null) - assertNotNull(userSwitcherController.mExitGuestDialog) - userSwitcherController.mExitGuestDialog - .getButton(DialogInterface.BUTTON_POSITIVE).performClick() - testableLooper.processAllMessages() - assertEquals(1, uiEventLogger.numLogs()) - assertTrue( - QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) || - QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0) - ) - } - - @Test - fun testRemoveGuest_removeButtonPressed_dialogDismissed() { - val currentGuestUserRecord = - UserRecord( - guestInfo, - picture, - true /* guest */, - true /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(guestInfo.id) - `when`(userTracker.userInfo).thenReturn(guestInfo) - - userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null) - assertNotNull(userSwitcherController.mExitGuestDialog) - userSwitcherController.mExitGuestDialog - .getButton(DialogInterface.BUTTON_POSITIVE).performClick() - testableLooper.processAllMessages() - assertFalse(userSwitcherController.mExitGuestDialog.isShowing) - } - - @Test - fun testRemoveGuest_dialogShowerUsed() { - val currentGuestUserRecord = - UserRecord( - guestInfo, - picture, - true /* guest */, - true /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(guestInfo.id) - `when`(userTracker.userInfo).thenReturn(guestInfo) - - userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower) - assertNotNull(userSwitcherController.mExitGuestDialog) - testableLooper.processAllMessages() - verify(dialogShower) - .showDialog( - userSwitcherController.mExitGuestDialog, - DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode")) - } - - @Test - fun testRemoveGuest_cancelButtonPressed_isNotLogged() { - val currentGuestUserRecord = - UserRecord( - guestInfo, - picture, - true /* guest */, - true /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(guestId) - `when`(userTracker.userInfo).thenReturn(guestInfo) - - userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null) - assertNotNull(userSwitcherController.mExitGuestDialog) - userSwitcherController.mExitGuestDialog - .getButton(DialogInterface.BUTTON_NEUTRAL).performClick() - testableLooper.processAllMessages() - assertEquals(0, uiEventLogger.numLogs()) - } - - @Test - fun testWipeGuest_startOverButtonPressed_isLogged() { - val currentGuestUserRecord = - UserRecord( - guestInfo, - picture, - true /* guest */, - false /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(guestId) - `when`(userTracker.userInfo).thenReturn(guestInfo) - - // Simulate that guest user has already logged in - `when`(secureSettings.getIntForUser( - eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt())) - .thenReturn(1) - - userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null) - - // Simulate a user switch event - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId) - - assertNotNull(userSwitcherController.mGuestResumeSessionReceiver) - userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent) - - assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog) - userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog - .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick() - testableLooper.processAllMessages() - assertEquals(1, uiEventLogger.numLogs()) - assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0)) - } - - @Test - fun testWipeGuest_continueButtonPressed_isLogged() { - val currentGuestUserRecord = - UserRecord( - guestInfo, - picture, - true /* guest */, - false /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - `when`(userTracker.userId).thenReturn(guestId) - `when`(userTracker.userInfo).thenReturn(guestInfo) - - // Simulate that guest user has already logged in - `when`(secureSettings.getIntForUser( - eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt())) - .thenReturn(1) - - userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null) - - // Simulate a user switch event - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId) - - assertNotNull(userSwitcherController.mGuestResumeSessionReceiver) - userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent) - - assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog) - userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog - .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE) - .performClick() - testableLooper.processAllMessages() - assertEquals(1, uiEventLogger.numLogs()) - assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0)) - } - - @Test - fun test_getCurrentUserName_shouldReturnNameOfTheCurrentUser() { - fun addUser(id: Int, name: String, isCurrent: Boolean) { - userSwitcherController.users.add( - UserRecord( - UserInfo(id, name, 0), - null, false, isCurrent, false, - false, false, false - ) - ) - } - val bgUserName = "background_user" - val fgUserName = "foreground_user" - - addUser(1, bgUserName, false) - addUser(2, fgUserName, true) - - assertEquals(fgUserName, userSwitcherController.currentUserName) - } - - @Test - fun isSystemUser_currentUserIsSystemUser_shouldReturnTrue() { - `when`(userTracker.userId).thenReturn(UserHandle.USER_SYSTEM) - assertEquals(true, userSwitcherController.isSystemUser) - } - - @Test - fun isSystemUser_currentUserIsNotSystemUser_shouldReturnFalse() { - `when`(userTracker.userId).thenReturn(1) - assertEquals(false, userSwitcherController.isSystemUser) - } - - @Test - fun testCanCreateSupervisedUserWithConfiguredPackage() { - // GIVEN the supervised user creation package is configured - `when`(context.getString( - com.android.internal.R.string.config_supervisedUserCreationPackage)) - .thenReturn("some_pkg") - - // AND the current user is allowed to create new users - `when`(userTracker.userId).thenReturn(ownerId) - `when`(userTracker.userInfo).thenReturn(ownerInfo) - - // WHEN the controller is started with the above config - setupController() - testableLooper.processAllMessages() - - // THEN a supervised user can be constructed - assertTrue(userSwitcherController.canCreateSupervisedUser()) - } - - @Test - fun testCannotCreateSupervisedUserWithConfiguredPackage() { - // GIVEN the supervised user creation package is NOT configured - `when`(context.getString( - com.android.internal.R.string.config_supervisedUserCreationPackage)) - .thenReturn(null) - - // AND the current user is allowed to create new users - `when`(userTracker.userId).thenReturn(ownerId) - `when`(userTracker.userInfo).thenReturn(ownerInfo) - - // WHEN the controller is started with the above config - setupController() - testableLooper.processAllMessages() - - // THEN a supervised user can NOT be constructed - assertFalse(userSwitcherController.canCreateSupervisedUser()) - } - - @Test - fun testCannotCreateUserWhenUserSwitcherDisabled() { - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(0) - setupController() - assertFalse(userSwitcherController.canCreateUser()) - } - - @Test - fun testCannotCreateGuestUserWhenUserSwitcherDisabled() { - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(0) - setupController() - assertFalse(userSwitcherController.canCreateGuest(false)) - } - - @Test - fun testCannotCreateSupervisedUserWhenUserSwitcherDisabled() { - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(0) - setupController() - assertFalse(userSwitcherController.canCreateSupervisedUser()) - } - - @Test - fun testCanManageUser_userSwitcherEnabled_addUserWhenLocked() { - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(1) - - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.ADD_USERS_WHEN_LOCKED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(1) - setupController() - assertTrue(userSwitcherController.canManageUsers()) - } - - @Test - fun testCanManageUser_userSwitcherDisabled_addUserWhenLocked() { - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(0) - - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.ADD_USERS_WHEN_LOCKED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(1) - setupController() - assertFalse(userSwitcherController.canManageUsers()) - } - - @Test - fun testCanManageUser_userSwitcherEnabled_isAdmin() { - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(1) - - setupController() - assertTrue(userSwitcherController.canManageUsers()) - } - - @Test - fun testCanManageUser_userSwitcherDisabled_isAdmin() { - `when`( - globalSettings.getIntForUser( - eq(Settings.Global.USER_SWITCHER_ENABLED), - anyInt(), - eq(UserHandle.USER_SYSTEM) - ) - ).thenReturn(0) - - setupController() - assertFalse(userSwitcherController.canManageUsers()) - } - - @Test - fun addUserSwitchCallback() { - val broadcastReceiverCaptor = argumentCaptor() - verify(broadcastDispatcher).registerReceiver( - capture(broadcastReceiverCaptor), - any(), - nullable(), nullable(), anyInt(), nullable()) - - val cb = mock(UserSwitcherController.UserSwitchCallback::class.java) - userSwitcherController.addUserSwitchCallback(cb) - - val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId) - broadcastReceiverCaptor.value.onReceive(context, intent) - verify(cb).onUserSwitched() - } - - @Test - fun onUserItemClicked_guest_runsOnBgThread() { - val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java) - val guestUserRecord = UserRecord( - null, - picture, - true /* guest */, - false /* current */, - false /* isAddUser */, - false /* isRestricted */, - true /* isSwitchToEnabled */, - false /* isAddSupervisedUser */ - ) - - userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower) - assertTrue(bgExecutor.numPending() > 0) - verify(userManager, never()).createGuest(context) - bgExecutor.runAllReady() - verify(userManager).createGuest(context) - } - - @Test - fun onUserItemClicked_manageUsers() { - val manageUserRecord = LegacyUserDataHelper.createRecord( - mContext, - ownerId, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - isRestricted = false, - isSwitchToEnabled = true - ) - - userSwitcherController.onUserListItemClicked(manageUserRecord, null) - val intentCaptor = kotlinArgumentCaptor() - verify(activityStarter).startActivity(intentCaptor.capture(), - eq(true) - ) - Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt deleted file mode 100644 index 7c7f0e1e0e12d..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplRefactoredTest.kt +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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.data.repository - -import android.content.pm.UserInfo -import android.os.UserHandle -import android.os.UserManager -import android.provider.Settings -import androidx.test.filters.SmallTest -import com.android.systemui.user.data.model.UserSwitcherSettingsModel -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Mockito.`when` as whenever - -@SmallTest -@RunWith(JUnit4::class) -class UserRepositoryImplRefactoredTest : UserRepositoryImplTest() { - - @Before - fun setUp() { - super.setUp(isRefactored = true) - } - - @Test - fun userSwitcherSettings() = runSelfCancelingTest { - setUpGlobalSettings( - isSimpleUserSwitcher = true, - isAddUsersFromLockscreen = true, - isUserSwitcherEnabled = true, - ) - underTest = create(this) - - var value: UserSwitcherSettingsModel? = null - underTest.userSwitcherSettings.onEach { value = it }.launchIn(this) - - assertUserSwitcherSettings( - model = value, - expectedSimpleUserSwitcher = true, - expectedAddUsersFromLockscreen = true, - expectedUserSwitcherEnabled = true, - ) - - setUpGlobalSettings( - isSimpleUserSwitcher = false, - isAddUsersFromLockscreen = true, - isUserSwitcherEnabled = true, - ) - assertUserSwitcherSettings( - model = value, - expectedSimpleUserSwitcher = false, - expectedAddUsersFromLockscreen = true, - expectedUserSwitcherEnabled = true, - ) - } - - @Test - fun refreshUsers() = runSelfCancelingTest { - underTest = create(this) - val initialExpectedValue = - setUpUsers( - count = 3, - selectedIndex = 0, - ) - var userInfos: List? = null - var selectedUserInfo: UserInfo? = null - underTest.userInfos.onEach { userInfos = it }.launchIn(this) - underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this) - - underTest.refreshUsers() - assertThat(userInfos).isEqualTo(initialExpectedValue) - assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0]) - assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id) - - val secondExpectedValue = - setUpUsers( - count = 4, - selectedIndex = 1, - ) - underTest.refreshUsers() - assertThat(userInfos).isEqualTo(secondExpectedValue) - assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1]) - assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id) - - val selectedNonGuestUserId = selectedUserInfo?.id - val thirdExpectedValue = - setUpUsers( - count = 2, - isLastGuestUser = true, - selectedIndex = 1, - ) - underTest.refreshUsers() - assertThat(userInfos).isEqualTo(thirdExpectedValue) - assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1]) - assertThat(selectedUserInfo?.isGuest).isTrue() - assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId) - } - - @Test - fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest { - underTest = create(this) - val unsortedUsers = - setUpUsers( - count = 3, - selectedIndex = 0, - isLastGuestUser = true, - ) - unsortedUsers[0].creationTime = 999 - unsortedUsers[1].creationTime = 900 - unsortedUsers[2].creationTime = 950 - val expectedUsers = - listOf( - unsortedUsers[1], - unsortedUsers[0], - unsortedUsers[2], // last because this is the guest - ) - var userInfos: List? = null - underTest.userInfos.onEach { userInfos = it }.launchIn(this) - - underTest.refreshUsers() - assertThat(userInfos).isEqualTo(expectedUsers) - } - - @Test - fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest { - underTest = create(this) - var selectedUserInfo: UserInfo? = null - underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this) - setUpUsers( - count = 2, - selectedIndex = 0, - ) - tracker.onProfileChanged() - assertThat(selectedUserInfo?.id == 0) - setUpUsers( - count = 2, - selectedIndex = 1, - ) - tracker.onProfileChanged() - assertThat(selectedUserInfo?.id == 1) - } - - private fun setUpUsers( - count: Int, - isLastGuestUser: Boolean = false, - selectedIndex: Int = 0, - ): List { - val userInfos = - (0 until count).map { index -> - createUserInfo( - index, - isGuest = isLastGuestUser && index == count - 1, - ) - } - whenever(manager.aliveUsers).thenReturn(userInfos) - tracker.set(userInfos, selectedIndex) - return userInfos - } - - private fun createUserInfo( - id: Int, - isGuest: Boolean, - ): UserInfo { - val flags = 0 - return UserInfo( - id, - "user_$id", - /* iconPath= */ "", - flags, - if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags), - ) - } - - private fun setUpGlobalSettings( - isSimpleUserSwitcher: Boolean = false, - isAddUsersFromLockscreen: Boolean = false, - isUserSwitcherEnabled: Boolean = true, - ) { - context.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_expandLockScreenUserSwitcher, - true, - ) - globalSettings.putIntForUser( - UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER, - if (isSimpleUserSwitcher) 1 else 0, - UserHandle.USER_SYSTEM, - ) - globalSettings.putIntForUser( - Settings.Global.ADD_USERS_WHEN_LOCKED, - if (isAddUsersFromLockscreen) 1 else 0, - UserHandle.USER_SYSTEM, - ) - globalSettings.putIntForUser( - Settings.Global.USER_SWITCHER_ENABLED, - if (isUserSwitcherEnabled) 1 else 0, - UserHandle.USER_SYSTEM, - ) - } - - private fun assertUserSwitcherSettings( - model: UserSwitcherSettingsModel?, - expectedSimpleUserSwitcher: Boolean, - expectedAddUsersFromLockscreen: Boolean, - expectedUserSwitcherEnabled: Boolean, - ) { - checkNotNull(model) - assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher) - assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen) - assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled) - } - - /** - * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which - * is then automatically canceled and cleaned-up. - */ - private fun runSelfCancelingTest( - block: suspend CoroutineScope.() -> Unit, - ) = - runBlocking(Dispatchers.Main.immediate) { - val scope = CoroutineScope(coroutineContext + Job()) - block(scope) - scope.cancel() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index dcea83a55a747..2e527be1af89e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -17,54 +17,263 @@ package com.android.systemui.user.data.repository +import android.content.pm.UserInfo +import android.os.UserHandle import android.os.UserManager +import android.provider.Settings +import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.settings.FakeUserTracker -import com.android.systemui.statusbar.policy.UserSwitcherController +import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineScope +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 import org.mockito.Mock +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -abstract class UserRepositoryImplTest : SysuiTestCase() { +@SmallTest +@RunWith(JUnit4::class) +class UserRepositoryImplTest : SysuiTestCase() { - @Mock protected lateinit var manager: UserManager - @Mock protected lateinit var controller: UserSwitcherController + @Mock private lateinit var manager: UserManager - protected lateinit var underTest: UserRepositoryImpl + private lateinit var underTest: UserRepositoryImpl - protected lateinit var globalSettings: FakeSettings - protected lateinit var tracker: FakeUserTracker - protected lateinit var featureFlags: FakeFeatureFlags + private lateinit var globalSettings: FakeSettings + private lateinit var tracker: FakeUserTracker - protected fun setUp(isRefactored: Boolean) { + @Before + fun setUp() { MockitoAnnotations.initMocks(this) globalSettings = FakeSettings() tracker = FakeUserTracker() - featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored) } - protected fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl { + @Test + fun userSwitcherSettings() = runSelfCancelingTest { + setUpGlobalSettings( + isSimpleUserSwitcher = true, + isAddUsersFromLockscreen = true, + isUserSwitcherEnabled = true, + ) + underTest = create(this) + + var value: UserSwitcherSettingsModel? = null + underTest.userSwitcherSettings.onEach { value = it }.launchIn(this) + + assertUserSwitcherSettings( + model = value, + expectedSimpleUserSwitcher = true, + expectedAddUsersFromLockscreen = true, + expectedUserSwitcherEnabled = true, + ) + + setUpGlobalSettings( + isSimpleUserSwitcher = false, + isAddUsersFromLockscreen = true, + isUserSwitcherEnabled = true, + ) + assertUserSwitcherSettings( + model = value, + expectedSimpleUserSwitcher = false, + expectedAddUsersFromLockscreen = true, + expectedUserSwitcherEnabled = true, + ) + } + + @Test + fun refreshUsers() = runSelfCancelingTest { + underTest = create(this) + val initialExpectedValue = + setUpUsers( + count = 3, + selectedIndex = 0, + ) + var userInfos: List? = null + var selectedUserInfo: UserInfo? = null + underTest.userInfos.onEach { userInfos = it }.launchIn(this) + underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this) + + underTest.refreshUsers() + assertThat(userInfos).isEqualTo(initialExpectedValue) + assertThat(selectedUserInfo).isEqualTo(initialExpectedValue[0]) + assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id) + + val secondExpectedValue = + setUpUsers( + count = 4, + selectedIndex = 1, + ) + underTest.refreshUsers() + assertThat(userInfos).isEqualTo(secondExpectedValue) + assertThat(selectedUserInfo).isEqualTo(secondExpectedValue[1]) + assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedUserInfo?.id) + + val selectedNonGuestUserId = selectedUserInfo?.id + val thirdExpectedValue = + setUpUsers( + count = 2, + isLastGuestUser = true, + selectedIndex = 1, + ) + underTest.refreshUsers() + assertThat(userInfos).isEqualTo(thirdExpectedValue) + assertThat(selectedUserInfo).isEqualTo(thirdExpectedValue[1]) + assertThat(selectedUserInfo?.isGuest).isTrue() + assertThat(underTest.lastSelectedNonGuestUserId).isEqualTo(selectedNonGuestUserId) + } + + @Test + fun `refreshUsers - sorts by creation time - guest user last`() = runSelfCancelingTest { + underTest = create(this) + val unsortedUsers = + setUpUsers( + count = 3, + selectedIndex = 0, + isLastGuestUser = true, + ) + unsortedUsers[0].creationTime = 999 + unsortedUsers[1].creationTime = 900 + unsortedUsers[2].creationTime = 950 + val expectedUsers = + listOf( + unsortedUsers[1], + unsortedUsers[0], + unsortedUsers[2], // last because this is the guest + ) + var userInfos: List? = null + underTest.userInfos.onEach { userInfos = it }.launchIn(this) + + underTest.refreshUsers() + assertThat(userInfos).isEqualTo(expectedUsers) + } + + private fun setUpUsers( + count: Int, + isLastGuestUser: Boolean = false, + selectedIndex: Int = 0, + ): List { + val userInfos = + (0 until count).map { index -> + createUserInfo( + index, + isGuest = isLastGuestUser && index == count - 1, + ) + } + whenever(manager.aliveUsers).thenReturn(userInfos) + tracker.set(userInfos, selectedIndex) + return userInfos + } + @Test + fun `userTrackerCallback - updates selectedUserInfo`() = runSelfCancelingTest { + underTest = create(this) + var selectedUserInfo: UserInfo? = null + underTest.selectedUserInfo.onEach { selectedUserInfo = it }.launchIn(this) + setUpUsers( + count = 2, + selectedIndex = 0, + ) + tracker.onProfileChanged() + assertThat(selectedUserInfo?.id).isEqualTo(0) + setUpUsers( + count = 2, + selectedIndex = 1, + ) + tracker.onProfileChanged() + assertThat(selectedUserInfo?.id).isEqualTo(1) + } + + private fun createUserInfo( + id: Int, + isGuest: Boolean, + ): UserInfo { + val flags = 0 + return UserInfo( + id, + "user_$id", + /* iconPath= */ "", + flags, + if (isGuest) UserManager.USER_TYPE_FULL_GUEST else UserInfo.getDefaultUserType(flags), + ) + } + + private fun setUpGlobalSettings( + isSimpleUserSwitcher: Boolean = false, + isAddUsersFromLockscreen: Boolean = false, + isUserSwitcherEnabled: Boolean = true, + ) { + context.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_expandLockScreenUserSwitcher, + true, + ) + globalSettings.putIntForUser( + UserRepositoryImpl.SETTING_SIMPLE_USER_SWITCHER, + if (isSimpleUserSwitcher) 1 else 0, + UserHandle.USER_SYSTEM, + ) + globalSettings.putIntForUser( + Settings.Global.ADD_USERS_WHEN_LOCKED, + if (isAddUsersFromLockscreen) 1 else 0, + UserHandle.USER_SYSTEM, + ) + globalSettings.putIntForUser( + Settings.Global.USER_SWITCHER_ENABLED, + if (isUserSwitcherEnabled) 1 else 0, + UserHandle.USER_SYSTEM, + ) + } + + private fun assertUserSwitcherSettings( + model: UserSwitcherSettingsModel?, + expectedSimpleUserSwitcher: Boolean, + expectedAddUsersFromLockscreen: Boolean, + expectedUserSwitcherEnabled: Boolean, + ) { + checkNotNull(model) + assertThat(model.isSimpleUserSwitcher).isEqualTo(expectedSimpleUserSwitcher) + assertThat(model.isAddUsersFromLockscreen).isEqualTo(expectedAddUsersFromLockscreen) + assertThat(model.isUserSwitcherEnabled).isEqualTo(expectedUserSwitcherEnabled) + } + + /** + * Executes the given block of execution within the scope of a dedicated [CoroutineScope] which + * is then automatically canceled and cleaned-up. + */ + private fun runSelfCancelingTest( + block: suspend CoroutineScope.() -> Unit, + ) = + runBlocking(Dispatchers.Main.immediate) { + val scope = CoroutineScope(coroutineContext + Job()) + block(scope) + scope.cancel() + } + + private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl { return UserRepositoryImpl( appContext = context, manager = manager, - controller = controller, applicationScope = scope, mainDispatcher = IMMEDIATE, backgroundDispatcher = IMMEDIATE, globalSettings = globalSettings, tracker = tracker, - featureFlags = featureFlags, ) } companion object { - @JvmStatic protected val IMMEDIATE = Dispatchers.Main.immediate + @JvmStatic private val IMMEDIATE = Dispatchers.Main.immediate } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt deleted file mode 100644 index a363a037c4992..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplUnrefactoredTest.kt +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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.data.repository - -import android.content.pm.UserInfo -import androidx.test.filters.SmallTest -import com.android.systemui.statusbar.policy.UserSwitcherController -import com.android.systemui.user.data.source.UserRecord -import com.android.systemui.user.shared.model.UserActionModel -import com.android.systemui.user.shared.model.UserModel -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.ArgumentCaptor -import org.mockito.Captor -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever - -@SmallTest -@RunWith(JUnit4::class) -class UserRepositoryImplUnrefactoredTest : UserRepositoryImplTest() { - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - } - - @Captor - private lateinit var userSwitchCallbackCaptor: - ArgumentCaptor - - @Before - fun setUp() { - super.setUp(isRefactored = false) - - whenever(controller.isAddUsersFromLockScreenEnabled).thenReturn(MutableStateFlow(false)) - whenever(controller.isGuestUserAutoCreated).thenReturn(false) - whenever(controller.isGuestUserResetting).thenReturn(false) - - underTest = create() - } - - @Test - fun `users - registers for updates`() = - runBlocking(IMMEDIATE) { - val job = underTest.users.onEach {}.launchIn(this) - - verify(controller).addUserSwitchCallback(any()) - - job.cancel() - } - - @Test - fun `users - unregisters from updates`() = - runBlocking(IMMEDIATE) { - val job = underTest.users.onEach {}.launchIn(this) - verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor)) - - job.cancel() - - verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value) - } - - @Test - fun `users - does not include actions`() = - runBlocking(IMMEDIATE) { - whenever(controller.users) - .thenReturn( - arrayListOf( - createUserRecord(0, isSelected = true), - createActionRecord(UserActionModel.ADD_USER), - createUserRecord(1), - createUserRecord(2), - createActionRecord(UserActionModel.ADD_SUPERVISED_USER), - createActionRecord(UserActionModel.ENTER_GUEST_MODE), - createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT), - ) - ) - var models: List? = null - val job = underTest.users.onEach { models = it }.launchIn(this) - - assertThat(models).hasSize(3) - assertThat(models?.get(0)?.id).isEqualTo(0) - assertThat(models?.get(0)?.isSelected).isTrue() - assertThat(models?.get(1)?.id).isEqualTo(1) - assertThat(models?.get(1)?.isSelected).isFalse() - assertThat(models?.get(2)?.id).isEqualTo(2) - assertThat(models?.get(2)?.isSelected).isFalse() - job.cancel() - } - - @Test - fun selectedUser() = - runBlocking(IMMEDIATE) { - whenever(controller.users) - .thenReturn( - arrayListOf( - createUserRecord(0, isSelected = true), - createUserRecord(1), - createUserRecord(2), - ) - ) - var id: Int? = null - val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this) - - assertThat(id).isEqualTo(0) - - whenever(controller.users) - .thenReturn( - arrayListOf( - createUserRecord(0), - createUserRecord(1), - createUserRecord(2, isSelected = true), - ) - ) - verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor)) - userSwitchCallbackCaptor.value.onUserSwitched() - assertThat(id).isEqualTo(2) - - job.cancel() - } - - @Test - fun `actions - unregisters from updates`() = - runBlocking(IMMEDIATE) { - val job = underTest.actions.onEach {}.launchIn(this) - verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor)) - - job.cancel() - - verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value) - } - - @Test - fun `actions - registers for updates`() = - runBlocking(IMMEDIATE) { - val job = underTest.actions.onEach {}.launchIn(this) - - verify(controller).addUserSwitchCallback(any()) - - job.cancel() - } - - @Test - fun `actions - does not include users`() = - runBlocking(IMMEDIATE) { - whenever(controller.users) - .thenReturn( - arrayListOf( - createUserRecord(0, isSelected = true), - createActionRecord(UserActionModel.ADD_USER), - createUserRecord(1), - createUserRecord(2), - createActionRecord(UserActionModel.ADD_SUPERVISED_USER), - createActionRecord(UserActionModel.ENTER_GUEST_MODE), - createActionRecord(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT), - ) - ) - var models: List? = null - val job = underTest.actions.onEach { models = it }.launchIn(this) - - assertThat(models).hasSize(4) - assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER) - assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER) - assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE) - assertThat(models?.get(3)).isEqualTo(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) - job.cancel() - } - - private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord { - return UserRecord( - info = UserInfo(id, "name$id", 0), - isCurrent = isSelected, - ) - } - - private fun createActionRecord(action: UserActionModel): UserRecord { - return UserRecord( - isAddUser = action == UserActionModel.ADD_USER, - isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER, - isGuest = action == UserActionModel.ENTER_GUEST_MODE, - isManageUsers = action == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - ) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt deleted file mode 100644 index f682e31c05471..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorRefactoredTest.kt +++ /dev/null @@ -1,740 +0,0 @@ -/* - * 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.domain.interactor - -import android.content.Intent -import android.content.pm.UserInfo -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import android.os.UserHandle -import android.os.UserManager -import android.provider.Settings -import androidx.test.filters.SmallTest -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.qs.user.UserSwitchDialogController -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 -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.kotlinArgumentCaptor -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.advanceUntilIdle -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -@SmallTest -@RunWith(JUnit4::class) -class UserInteractorRefactoredTest : UserInteractorTest() { - - override fun isRefactored(): Boolean { - return true - } - - @Before - override fun setUp() { - 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, - ) - whenever(manager.getUserIcon(anyInt())).thenReturn(ICON) - whenever(manager.canAddMoreUsers(any())).thenReturn(true) - } - - @Test - fun `onRecordSelected - user`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 3, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - - underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower) - - verify(dialogShower).dismiss() - verify(activityManager).switchUser(userInfos[1].id) - Unit - } - - @Test - fun `onRecordSelected - switch to guest user`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 3, includeGuest = true) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - - underTest.onRecordSelected(UserRecord(info = userInfos.last())) - - verify(activityManager).switchUser(userInfos.last().id) - Unit - } - - @Test - fun `onRecordSelected - enter guest mode`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 3, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true) - whenever(manager.createGuest(any())).thenReturn(guestUserInfo) - - underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower) - - verify(dialogShower).dismiss() - verify(manager).createGuest(any()) - Unit - } - - @Test - fun `onRecordSelected - action`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 3, includeGuest = true) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - - underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower) - - verify(dialogShower, never()).dismiss() - verify(activityStarter).startActivity(any(), anyBoolean()) - } - - @Test - fun `users - switcher enabled`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 3, includeGuest = true) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - - var value: List? = null - val job = underTest.users.onEach { value = it }.launchIn(this) - assertUsers(models = value, count = 3, includeGuest = true) - - job.cancel() - } - - @Test - fun `users - switches to second user`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - - var value: List? = null - val job = underTest.users.onEach { value = it }.launchIn(this) - userRepository.setSelectedUserInfo(userInfos[1]) - - assertUsers(models = value, count = 2, selectedIndex = 1) - job.cancel() - } - - @Test - fun `users - switcher not enabled`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false)) - - var value: List? = null - val job = underTest.users.onEach { value = it }.launchIn(this) - assertUsers(models = value, count = 1) - - job.cancel() - } - - @Test - fun selectedUser() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - - var value: UserModel? = null - val job = underTest.selectedUser.onEach { value = it }.launchIn(this) - assertUser(value, id = 0, isSelected = true) - - userRepository.setSelectedUserInfo(userInfos[1]) - assertUser(value, id = 1, isSelected = true) - - job.cancel() - } - - @Test - fun `actions - device unlocked`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - keyguardRepository.setKeyguardShowing(false) - var value: List? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) - - assertThat(value) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - ) - ) - - job.cancel() - } - - @Test - fun `actions - device unlocked user not primary - empty list`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[1]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - keyguardRepository.setKeyguardShowing(false) - var value: List? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) - - assertThat(value).isEqualTo(emptyList()) - - job.cancel() - } - - @Test - fun `actions - device unlocked user is guest - empty list`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = true) - assertThat(userInfos[1].isGuest).isTrue() - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[1]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - keyguardRepository.setKeyguardShowing(false) - var value: List? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) - - assertThat(value).isEqualTo(emptyList()) - - job.cancel() - } - - @Test - fun `actions - device locked add from lockscreen set - full list`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings( - UserSwitcherSettingsModel( - isUserSwitcherEnabled = true, - isAddUsersFromLockscreen = true, - ) - ) - keyguardRepository.setKeyguardShowing(false) - var value: List? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) - - assertThat(value) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - ) - ) - - job.cancel() - } - - @Test - fun `actions - device locked - only guest action and manage user is shown`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - keyguardRepository.setKeyguardShowing(true) - var value: List? = null - val job = underTest.actions.onEach { value = it }.launchIn(this) - - assertThat(value) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT - ) - ) - - job.cancel() - } - - @Test - fun `executeAction - add user - dialog shown`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - keyguardRepository.setKeyguardShowing(false) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) - val dialogShower: UserSwitchDialogController.DialogShower = mock() - - underTest.executeAction(UserActionModel.ADD_USER, dialogShower) - assertThat(dialogRequest) - .isEqualTo( - ShowDialogRequestModel.ShowAddUserDialog( - userHandle = userInfos[0].userHandle, - isKeyguardShowing = false, - showEphemeralMessage = false, - dialogShower = dialogShower, - ) - ) - - underTest.onDialogShown() - assertThat(dialogRequest).isNull() - - job.cancel() - } - - @Test - fun `executeAction - add supervised user - starts activity`() = - runBlocking(IMMEDIATE) { - underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER) - - val intentCaptor = kotlinArgumentCaptor() - verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) - assertThat(intentCaptor.value.action) - .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER) - assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE) - } - - @Test - fun `executeAction - navigate to manage users`() = - runBlocking(IMMEDIATE) { - underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) - - val intentCaptor = kotlinArgumentCaptor() - verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) - assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS) - } - - @Test - fun `executeAction - guest mode`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true) - whenever(manager.createGuest(any())).thenReturn(guestUserInfo) - val dialogRequests = mutableListOf() - val showDialogsJob = - underTest.dialogShowRequests - .onEach { - dialogRequests.add(it) - if (it != null) { - underTest.onDialogShown() - } - } - .launchIn(this) - val dismissDialogsJob = - underTest.dialogDismissRequests - .onEach { - if (it != null) { - underTest.onDialogDismissed() - } - } - .launchIn(this) - - underTest.executeAction(UserActionModel.ENTER_GUEST_MODE) - - assertThat(dialogRequests) - .contains( - ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true), - ) - verify(activityManager).switchUser(guestUserInfo.id) - - showDialogsJob.cancel() - dismissDialogsJob.cancel() - } - - @Test - fun `selectUser - already selected guest re-selected - exit guest dialog`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = true) - val guestUserInfo = userInfos[1] - assertThat(guestUserInfo.isGuest).isTrue() - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(guestUserInfo) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) - - underTest.selectUser( - newlySelectedUserId = guestUserInfo.id, - dialogShower = dialogShower, - ) - - assertThat(dialogRequest) - .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java) - verify(dialogShower, never()).dismiss() - job.cancel() - } - - @Test - fun `selectUser - currently guest non-guest selected - exit guest dialog`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = true) - val guestUserInfo = userInfos[1] - assertThat(guestUserInfo.isGuest).isTrue() - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(guestUserInfo) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) - - underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower) - - assertThat(dialogRequest) - .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java) - verify(dialogShower, never()).dismiss() - job.cancel() - } - - @Test - fun `selectUser - not currently guest - switches users`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - var dialogRequest: ShowDialogRequestModel? = null - val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) - - underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower) - - assertThat(dialogRequest).isNull() - verify(activityManager).switchUser(userInfos[1].id) - verify(dialogShower).dismiss() - job.cancel() - } - - @Test - fun `Telephony call state changes - refreshes users`() = - runBlocking(IMMEDIATE) { - val refreshUsersCallCount = userRepository.refreshUsersCallCount - - telephonyRepository.setCallState(1) - - assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) - } - - @Test - fun `User switched broadcast`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) - val callback1: UserInteractor.UserCallback = mock() - val callback2: UserInteractor.UserCallback = mock() - underTest.addCallback(callback1) - underTest.addCallback(callback2) - val refreshUsersCallCount = userRepository.refreshUsersCallCount - - userRepository.setSelectedUserInfo(userInfos[1]) - fakeBroadcastDispatcher.registeredReceivers.forEach { - it.onReceive( - context, - Intent(Intent.ACTION_USER_SWITCHED) - .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id), - ) - } - - verify(callback1).onUserStateChanged() - verify(callback2).onUserStateChanged() - assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id) - assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) - } - - @Test - fun `User info changed broadcast`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - val refreshUsersCallCount = userRepository.refreshUsersCallCount - - fakeBroadcastDispatcher.registeredReceivers.forEach { - it.onReceive( - context, - Intent(Intent.ACTION_USER_INFO_CHANGED), - ) - } - - assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) - } - - @Test - fun `System user unlocked broadcast - refresh users`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - val refreshUsersCallCount = userRepository.refreshUsersCallCount - - fakeBroadcastDispatcher.registeredReceivers.forEach { - it.onReceive( - context, - Intent(Intent.ACTION_USER_UNLOCKED) - .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM), - ) - } - - assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) - } - - @Test - fun `Non-system user unlocked broadcast - do not refresh users`() = - runBlocking(IMMEDIATE) { - val userInfos = createUserInfos(count = 2, includeGuest = false) - userRepository.setUserInfos(userInfos) - userRepository.setSelectedUserInfo(userInfos[0]) - val refreshUsersCallCount = userRepository.refreshUsersCallCount - - fakeBroadcastDispatcher.registeredReceivers.forEach { - it.onReceive( - context, - Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337), - ) - } - - 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, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - ), - ) - } - - @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?, - count: Int, - selectedIndex: Int = 0, - includeGuest: Boolean = false, - ) { - checkNotNull(models) - assertThat(models.size).isEqualTo(count) - models.forEachIndexed { index, model -> - assertUser( - model = model, - id = index, - isSelected = index == selectedIndex, - isGuest = includeGuest && index == count - 1 - ) - } - } - - private fun assertUser( - model: UserModel?, - id: Int, - isSelected: Boolean = false, - isGuest: Boolean = false, - ) { - checkNotNull(model) - assertThat(model.id).isEqualTo(id) - assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id")) - assertThat(model.isSelected).isEqualTo(isSelected) - assertThat(model.isSelectable).isTrue() - assertThat(model.isGuest).isEqualTo(isGuest) - } - - private fun assertRecords( - records: List, - userIds: List, - selectedUserIndex: Int = 0, - includeGuest: Boolean = false, - expectedActions: List = 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, - ): List { - return (0 until count).map { index -> - val isGuest = includeGuest && index == count - 1 - createUserInfo( - id = index, - name = - if (isGuest) { - "guest" - } else { - "user_$index" - }, - isPrimary = !isGuest && index == 0, - isGuest = isGuest, - ) - } - } - - private fun createUserInfo( - id: Int, - name: String, - isPrimary: Boolean = false, - isGuest: Boolean = false, - ): UserInfo { - return UserInfo( - id, - name, - /* iconPath= */ "", - /* flags= */ if (isPrimary) { - UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN - } else { - 0 - }, - if (isGuest) { - UserManager.USER_TYPE_FULL_GUEST - } else { - UserManager.USER_TYPE_FULL_SYSTEM - }, - ) - } - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - private val GUEST_ICON: Drawable = mock() - private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index 58f55314c1b6a..8fb98c12d6ff2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -19,51 +19,90 @@ package com.android.systemui.user.domain.interactor import android.app.ActivityManager import android.app.admin.DevicePolicyManager +import android.content.Intent +import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.os.UserHandle import android.os.UserManager +import android.provider.Settings +import androidx.test.filters.SmallTest +import com.android.internal.R.drawable.ic_account_circle import com.android.internal.logging.UiEventLogger import com.android.systemui.GuestResetOrExitSessionReceiver import com.android.systemui.GuestResumeSessionReceiver +import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.common.shared.model.Text import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.user.UserSwitchDialogController import com.android.systemui.statusbar.policy.DeviceProvisionedController -import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor +import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.FakeUserRepository +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 +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.kotlinArgumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.advanceUntilIdle +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -abstract class UserInteractorTest : SysuiTestCase() { +@SmallTest +@RunWith(JUnit4::class) +class UserInteractorTest : SysuiTestCase() { - @Mock protected lateinit var controller: UserSwitcherController - @Mock protected lateinit var activityStarter: ActivityStarter - @Mock protected lateinit var manager: UserManager - @Mock protected lateinit var activityManager: ActivityManager - @Mock protected lateinit var deviceProvisionedController: DeviceProvisionedController - @Mock protected lateinit var devicePolicyManager: DevicePolicyManager - @Mock protected lateinit var uiEventLogger: UiEventLogger - @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var manager: UserManager + @Mock private lateinit var activityManager: ActivityManager + @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController + @Mock private lateinit var devicePolicyManager: DevicePolicyManager + @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var dialogShower: UserSwitchDialogController.DialogShower @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver - protected lateinit var underTest: UserInteractor + private lateinit var underTest: UserInteractor - protected lateinit var testCoroutineScope: TestCoroutineScope - protected lateinit var userRepository: FakeUserRepository - protected lateinit var keyguardRepository: FakeKeyguardRepository - protected lateinit var telephonyRepository: FakeTelephonyRepository + private lateinit var testCoroutineScope: TestCoroutineScope + private lateinit var userRepository: FakeUserRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var telephonyRepository: FakeTelephonyRepository - abstract fun isRefactored(): Boolean - - open fun setUp() { + @Before + fun setUp() { MockitoAnnotations.initMocks(this) + whenever(manager.getUserIcon(anyInt())).thenReturn(ICON) + whenever(manager.canAddMoreUsers(any())).thenReturn(true) + + 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, + ) userRepository = FakeUserRepository() keyguardRepository = FakeKeyguardRepository() @@ -79,16 +118,11 @@ abstract class UserInteractorTest : SysuiTestCase() { UserInteractor( applicationContext = context, repository = userRepository, - controller = controller, activityStarter = activityStarter, keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, ), - featureFlags = - FakeFeatureFlags().apply { - set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, !isRefactored()) - }, manager = manager, applicationScope = testCoroutineScope, telephonyInteractor = @@ -117,7 +151,665 @@ abstract class UserInteractorTest : SysuiTestCase() { ) } + @Test + fun `onRecordSelected - user`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 3, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + + underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower) + + verify(dialogShower).dismiss() + verify(activityManager).switchUser(userInfos[1].id) + Unit + } + + @Test + fun `onRecordSelected - switch to guest user`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 3, includeGuest = true) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + + underTest.onRecordSelected(UserRecord(info = userInfos.last())) + + verify(activityManager).switchUser(userInfos.last().id) + Unit + } + + @Test + fun `onRecordSelected - enter guest mode`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 3, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true) + whenever(manager.createGuest(any())).thenReturn(guestUserInfo) + + underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower) + + verify(dialogShower).dismiss() + verify(manager).createGuest(any()) + Unit + } + + @Test + fun `onRecordSelected - action`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 3, includeGuest = true) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + + underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower) + + verify(dialogShower, never()).dismiss() + verify(activityStarter).startActivity(any(), anyBoolean()) + } + + @Test + fun `users - switcher enabled`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 3, includeGuest = true) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + + var value: List? = null + val job = underTest.users.onEach { value = it }.launchIn(this) + assertUsers(models = value, count = 3, includeGuest = true) + + job.cancel() + } + + @Test + fun `users - switches to second user`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + + var value: List? = null + val job = underTest.users.onEach { value = it }.launchIn(this) + userRepository.setSelectedUserInfo(userInfos[1]) + + assertUsers(models = value, count = 2, selectedIndex = 1) + job.cancel() + } + + @Test + fun `users - switcher not enabled`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false)) + + var value: List? = null + val job = underTest.users.onEach { value = it }.launchIn(this) + assertUsers(models = value, count = 1) + + job.cancel() + } + + @Test + fun selectedUser() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + + var value: UserModel? = null + val job = underTest.selectedUser.onEach { value = it }.launchIn(this) + assertUser(value, id = 0, isSelected = true) + + userRepository.setSelectedUserInfo(userInfos[1]) + assertUser(value, id = 1, isSelected = true) + + job.cancel() + } + + @Test + fun `actions - device unlocked`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + keyguardRepository.setKeyguardShowing(false) + var value: List? = null + val job = underTest.actions.onEach { value = it }.launchIn(this) + + assertThat(value) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE, + UserActionModel.ADD_USER, + UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + ) + ) + + job.cancel() + } + + @Test + fun `actions - device unlocked user not primary - empty list`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[1]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + keyguardRepository.setKeyguardShowing(false) + var value: List? = null + val job = underTest.actions.onEach { value = it }.launchIn(this) + + assertThat(value).isEqualTo(emptyList()) + + job.cancel() + } + + @Test + fun `actions - device unlocked user is guest - empty list`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = true) + assertThat(userInfos[1].isGuest).isTrue() + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[1]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + keyguardRepository.setKeyguardShowing(false) + var value: List? = null + val job = underTest.actions.onEach { value = it }.launchIn(this) + + assertThat(value).isEqualTo(emptyList()) + + job.cancel() + } + + @Test + fun `actions - device locked add from lockscreen set - full list`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings( + UserSwitcherSettingsModel( + isUserSwitcherEnabled = true, + isAddUsersFromLockscreen = true, + ) + ) + keyguardRepository.setKeyguardShowing(false) + var value: List? = null + val job = underTest.actions.onEach { value = it }.launchIn(this) + + assertThat(value) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE, + UserActionModel.ADD_USER, + UserActionModel.ADD_SUPERVISED_USER, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + ) + ) + + job.cancel() + } + + @Test + fun `actions - device locked - only guest action and manage user is shown`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + keyguardRepository.setKeyguardShowing(true) + var value: List? = null + val job = underTest.actions.onEach { value = it }.launchIn(this) + + assertThat(value) + .isEqualTo( + listOf( + UserActionModel.ENTER_GUEST_MODE, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT + ) + ) + + job.cancel() + } + + @Test + fun `executeAction - add user - dialog shown`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + keyguardRepository.setKeyguardShowing(false) + var dialogRequest: ShowDialogRequestModel? = null + val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + val dialogShower: UserSwitchDialogController.DialogShower = mock() + + underTest.executeAction(UserActionModel.ADD_USER, dialogShower) + assertThat(dialogRequest) + .isEqualTo( + ShowDialogRequestModel.ShowAddUserDialog( + userHandle = userInfos[0].userHandle, + isKeyguardShowing = false, + showEphemeralMessage = false, + dialogShower = dialogShower, + ) + ) + + underTest.onDialogShown() + assertThat(dialogRequest).isNull() + + job.cancel() + } + + @Test + fun `executeAction - add supervised user - starts activity`() = + runBlocking(IMMEDIATE) { + underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER) + + val intentCaptor = kotlinArgumentCaptor() + verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) + assertThat(intentCaptor.value.action) + .isEqualTo(UserManager.ACTION_CREATE_SUPERVISED_USER) + assertThat(intentCaptor.value.`package`).isEqualTo(SUPERVISED_USER_CREATION_APP_PACKAGE) + } + + @Test + fun `executeAction - navigate to manage users`() = + runBlocking(IMMEDIATE) { + underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) + + val intentCaptor = kotlinArgumentCaptor() + verify(activityStarter).startActivity(intentCaptor.capture(), eq(true)) + assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS) + } + + @Test + fun `executeAction - guest mode`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + val guestUserInfo = createUserInfo(id = 1337, name = "guest", isGuest = true) + whenever(manager.createGuest(any())).thenReturn(guestUserInfo) + val dialogRequests = mutableListOf() + val showDialogsJob = + underTest.dialogShowRequests + .onEach { + dialogRequests.add(it) + if (it != null) { + underTest.onDialogShown() + } + } + .launchIn(this) + val dismissDialogsJob = + underTest.dialogDismissRequests + .onEach { + if (it != null) { + underTest.onDialogDismissed() + } + } + .launchIn(this) + + underTest.executeAction(UserActionModel.ENTER_GUEST_MODE) + + assertThat(dialogRequests) + .contains( + ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true), + ) + verify(activityManager).switchUser(guestUserInfo.id) + + showDialogsJob.cancel() + dismissDialogsJob.cancel() + } + + @Test + fun `selectUser - already selected guest re-selected - exit guest dialog`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = true) + val guestUserInfo = userInfos[1] + assertThat(guestUserInfo.isGuest).isTrue() + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(guestUserInfo) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + var dialogRequest: ShowDialogRequestModel? = null + val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + + underTest.selectUser( + newlySelectedUserId = guestUserInfo.id, + dialogShower = dialogShower, + ) + + assertThat(dialogRequest) + .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java) + verify(dialogShower, never()).dismiss() + job.cancel() + } + + @Test + fun `selectUser - currently guest non-guest selected - exit guest dialog`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = true) + val guestUserInfo = userInfos[1] + assertThat(guestUserInfo.isGuest).isTrue() + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(guestUserInfo) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + var dialogRequest: ShowDialogRequestModel? = null + val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + + underTest.selectUser(newlySelectedUserId = userInfos[0].id, dialogShower = dialogShower) + + assertThat(dialogRequest) + .isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java) + verify(dialogShower, never()).dismiss() + job.cancel() + } + + @Test + fun `selectUser - not currently guest - switches users`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + var dialogRequest: ShowDialogRequestModel? = null + val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this) + + underTest.selectUser(newlySelectedUserId = userInfos[1].id, dialogShower = dialogShower) + + assertThat(dialogRequest).isNull() + verify(activityManager).switchUser(userInfos[1].id) + verify(dialogShower).dismiss() + job.cancel() + } + + @Test + fun `Telephony call state changes - refreshes users`() = + runBlocking(IMMEDIATE) { + val refreshUsersCallCount = userRepository.refreshUsersCallCount + + telephonyRepository.setCallState(1) + + assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) + } + + @Test + fun `User switched broadcast`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true)) + val callback1: UserInteractor.UserCallback = mock() + val callback2: UserInteractor.UserCallback = mock() + underTest.addCallback(callback1) + underTest.addCallback(callback2) + val refreshUsersCallCount = userRepository.refreshUsersCallCount + + userRepository.setSelectedUserInfo(userInfos[1]) + fakeBroadcastDispatcher.registeredReceivers.forEach { + it.onReceive( + context, + Intent(Intent.ACTION_USER_SWITCHED) + .putExtra(Intent.EXTRA_USER_HANDLE, userInfos[1].id), + ) + } + + verify(callback1).onUserStateChanged() + verify(callback2).onUserStateChanged() + assertThat(userRepository.secondaryUserId).isEqualTo(userInfos[1].id) + assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) + } + + @Test + fun `User info changed broadcast`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + val refreshUsersCallCount = userRepository.refreshUsersCallCount + + fakeBroadcastDispatcher.registeredReceivers.forEach { + it.onReceive( + context, + Intent(Intent.ACTION_USER_INFO_CHANGED), + ) + } + + assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) + } + + @Test + fun `System user unlocked broadcast - refresh users`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + val refreshUsersCallCount = userRepository.refreshUsersCallCount + + fakeBroadcastDispatcher.registeredReceivers.forEach { + it.onReceive( + context, + Intent(Intent.ACTION_USER_UNLOCKED) + .putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_SYSTEM), + ) + } + + assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1) + } + + @Test + fun `Non-system user unlocked broadcast - do not refresh users`() = + runBlocking(IMMEDIATE) { + val userInfos = createUserInfos(count = 2, includeGuest = false) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + val refreshUsersCallCount = userRepository.refreshUsersCallCount + + fakeBroadcastDispatcher.registeredReceivers.forEach { + it.onReceive( + context, + Intent(Intent.ACTION_USER_UNLOCKED).putExtra(Intent.EXTRA_USER_HANDLE, 1337), + ) + } + + 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, + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, + ), + ) + } + + @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?, + count: Int, + selectedIndex: Int = 0, + includeGuest: Boolean = false, + ) { + checkNotNull(models) + assertThat(models.size).isEqualTo(count) + models.forEachIndexed { index, model -> + assertUser( + model = model, + id = index, + isSelected = index == selectedIndex, + isGuest = includeGuest && index == count - 1 + ) + } + } + + private fun assertUser( + model: UserModel?, + id: Int, + isSelected: Boolean = false, + isGuest: Boolean = false, + ) { + checkNotNull(model) + assertThat(model.id).isEqualTo(id) + assertThat(model.name).isEqualTo(Text.Loaded(if (isGuest) "guest" else "user_$id")) + assertThat(model.isSelected).isEqualTo(isSelected) + assertThat(model.isSelectable).isTrue() + assertThat(model.isGuest).isEqualTo(isGuest) + } + + private fun assertRecords( + records: List, + userIds: List, + selectedUserIndex: Int = 0, + includeGuest: Boolean = false, + expectedActions: List = 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, + ): List { + return (0 until count).map { index -> + val isGuest = includeGuest && index == count - 1 + createUserInfo( + id = index, + name = + if (isGuest) { + "guest" + } else { + "user_$index" + }, + isPrimary = !isGuest && index == 0, + isGuest = isGuest, + ) + } + } + + private fun createUserInfo( + id: Int, + name: String, + isPrimary: Boolean = false, + isGuest: Boolean = false, + ): UserInfo { + return UserInfo( + id, + name, + /* iconPath= */ "", + /* flags= */ if (isPrimary) { + UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN + } else { + 0 + }, + if (isGuest) { + UserManager.USER_TYPE_FULL_GUEST + } else { + UserManager.USER_TYPE_FULL_SYSTEM + }, + ) + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate + private val ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) + private val GUEST_ICON: Drawable = mock() + private const val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt deleted file mode 100644 index 6a17c8ddc63d7..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorUnrefactoredTest.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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.domain.interactor - -import androidx.test.filters.SmallTest -import com.android.systemui.user.shared.model.UserActionModel -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.nullable -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.verify - -@SmallTest -@RunWith(JUnit4::class) -open class UserInteractorUnrefactoredTest : UserInteractorTest() { - - override fun isRefactored(): Boolean { - return false - } - - @Before - override fun setUp() { - super.setUp() - } - - @Test - fun `actions - not actionable when locked and locked - no actions`() = - runBlocking(IMMEDIATE) { - userRepository.setActions(UserActionModel.values().toList()) - userRepository.setActionableWhenLocked(false) - keyguardRepository.setKeyguardShowing(true) - - var actions: List? = null - val job = underTest.actions.onEach { actions = it }.launchIn(this) - - assertThat(actions).isEmpty() - job.cancel() - } - - @Test - fun `actions - not actionable when locked and not locked`() = - runBlocking(IMMEDIATE) { - setActions() - userRepository.setActionableWhenLocked(false) - keyguardRepository.setKeyguardShowing(false) - - var actions: List? = null - val job = underTest.actions.onEach { actions = it }.launchIn(this) - - assertThat(actions) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - ) - ) - job.cancel() - } - - @Test - fun `actions - actionable when locked and not locked`() = - runBlocking(IMMEDIATE) { - setActions() - userRepository.setActionableWhenLocked(true) - keyguardRepository.setKeyguardShowing(false) - - var actions: List? = null - val job = underTest.actions.onEach { actions = it }.launchIn(this) - - assertThat(actions) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - ) - ) - job.cancel() - } - - @Test - fun `actions - actionable when locked and locked`() = - runBlocking(IMMEDIATE) { - setActions() - userRepository.setActionableWhenLocked(true) - keyguardRepository.setKeyguardShowing(true) - - var actions: List? = null - val job = underTest.actions.onEach { actions = it }.launchIn(this) - - assertThat(actions) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE, - UserActionModel.ADD_USER, - UserActionModel.ADD_SUPERVISED_USER, - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT, - ) - ) - job.cancel() - } - - @Test - fun selectUser() { - val userId = 3 - - underTest.selectUser(userId) - - verify(controller).onUserSelected(eq(userId), nullable()) - } - - @Test - fun `executeAction - guest`() { - underTest.executeAction(UserActionModel.ENTER_GUEST_MODE) - - verify(controller).createAndSwitchToGuestUser(nullable()) - } - - @Test - fun `executeAction - add user`() { - underTest.executeAction(UserActionModel.ADD_USER) - - verify(controller).showAddUserDialog(nullable()) - } - - @Test - fun `executeAction - add supervised user`() { - underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER) - - verify(controller).startSupervisedUserActivity() - } - - @Test - fun `executeAction - manage users`() { - underTest.executeAction(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) - - verify(activityStarter).startActivity(any(), anyBoolean()) - } - - private fun setActions() { - userRepository.setActions(UserActionModel.values().toList()) - } - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 116023aca6550..db136800a3cc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.user.ui.viewmodel import android.app.ActivityManager import android.app.admin.DevicePolicyManager -import android.graphics.drawable.Drawable +import android.content.pm.UserInfo import android.os.UserManager import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger @@ -27,32 +27,37 @@ import com.android.systemui.GuestResetOrExitSessionReceiver import com.android.systemui.GuestResumeSessionReceiver import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Text -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.plugins.ActivityStarter import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.statusbar.policy.DeviceProvisionedController -import com.android.systemui.statusbar.policy.UserSwitcherController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor +import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper import com.android.systemui.user.shared.model.UserActionModel -import com.android.systemui.user.shared.model.UserModel -import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope -import kotlinx.coroutines.yield +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -60,11 +65,11 @@ import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class UserSwitcherViewModelTest : SysuiTestCase() { - @Mock private lateinit var controller: UserSwitcherController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var activityManager: ActivityManager @Mock private lateinit var manager: UserManager @@ -80,28 +85,47 @@ class UserSwitcherViewModelTest : SysuiTestCase() { private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var powerRepository: FakePowerRepository + private lateinit var testDispatcher: TestDispatcher + private lateinit var testScope: TestScope + private lateinit var injectedScope: CoroutineScope + @Before fun setUp() { MockitoAnnotations.initMocks(this) + whenever(manager.canAddMoreUsers(any())).thenReturn(true) + whenever(manager.getUserSwitchability(any())) + .thenReturn(UserManager.SWITCHABILITY_STATUS_OK) + overrideResource( + com.android.internal.R.string.config_supervisedUserCreationPackage, + SUPERVISED_USER_CREATION_PACKAGE, + ) + testDispatcher = UnconfinedTestDispatcher() + testScope = TestScope(testDispatcher) + injectedScope = CoroutineScope(testScope.coroutineContext + SupervisorJob()) userRepository = FakeUserRepository() + runBlocking { + userRepository.setSettings( + UserSwitcherSettingsModel( + isUserSwitcherEnabled = true, + ) + ) + } + keyguardRepository = FakeKeyguardRepository() powerRepository = FakePowerRepository() - val featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER, true) - val scope = TestCoroutineScope() val refreshUsersScheduler = RefreshUsersScheduler( - applicationScope = scope, - mainDispatcher = IMMEDIATE, + applicationScope = injectedScope, + mainDispatcher = testDispatcher, repository = userRepository, ) val guestUserInteractor = GuestUserInteractor( applicationContext = context, - applicationScope = scope, - mainDispatcher = IMMEDIATE, - backgroundDispatcher = IMMEDIATE, + applicationScope = injectedScope, + mainDispatcher = testDispatcher, + backgroundDispatcher = testDispatcher, manager = manager, repository = userRepository, deviceProvisionedController = deviceProvisionedController, @@ -118,21 +142,19 @@ class UserSwitcherViewModelTest : SysuiTestCase() { UserInteractor( applicationContext = context, repository = userRepository, - controller = controller, activityStarter = activityStarter, keyguardInteractor = KeyguardInteractor( repository = keyguardRepository, ), - featureFlags = featureFlags, manager = manager, - applicationScope = scope, + applicationScope = injectedScope, telephonyInteractor = TelephonyInteractor( repository = FakeTelephonyRepository(), ), broadcastDispatcher = fakeBroadcastDispatcher, - backgroundDispatcher = IMMEDIATE, + backgroundDispatcher = testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestUserInteractor, @@ -141,222 +163,216 @@ class UserSwitcherViewModelTest : SysuiTestCase() { PowerInteractor( repository = powerRepository, ), - featureFlags = featureFlags, guestUserInteractor = guestUserInteractor, ) .create(UserSwitcherViewModel::class.java) } @Test - fun users() = - runBlocking(IMMEDIATE) { - userRepository.setUsers( + fun users() = selfCancelingTest { + val userInfos = + listOf( + UserInfo( + /* id= */ 0, + /* name= */ "zero", + /* iconPath= */ "", + /* flags= */ UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 1, + /* name= */ "one", + /* iconPath= */ "", + /* flags= */ 0, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + UserInfo( + /* id= */ 2, + /* name= */ "two", + /* iconPath= */ "", + /* flags= */ 0, + UserManager.USER_TYPE_FULL_SYSTEM, + ), + ) + userRepository.setUserInfos(userInfos) + userRepository.setSelectedUserInfo(userInfos[0]) + + val userViewModels = mutableListOf>() + val job = launch(testDispatcher) { underTest.users.toList(userViewModels) } + + assertThat(userViewModels.last()).hasSize(3) + assertUserViewModel( + viewModel = userViewModels.last()[0], + viewKey = 0, + name = "zero", + isSelectionMarkerVisible = true, + ) + assertUserViewModel( + viewModel = userViewModels.last()[1], + viewKey = 1, + name = "one", + isSelectionMarkerVisible = false, + ) + assertUserViewModel( + viewModel = userViewModels.last()[2], + viewKey = 2, + name = "two", + isSelectionMarkerVisible = false, + ) + job.cancel() + } + + @Test + fun `maximumUserColumns - few users`() = selfCancelingTest { + setUsers(count = 2) + val values = mutableListOf() + val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } + + assertThat(values.last()).isEqualTo(4) + + job.cancel() + } + + @Test + fun `maximumUserColumns - many users`() = selfCancelingTest { + setUsers(count = 5) + val values = mutableListOf() + val job = launch(testDispatcher) { underTest.maximumUserColumns.toList(values) } + + assertThat(values.last()).isEqualTo(3) + job.cancel() + } + + @Test + fun `isOpenMenuButtonVisible - has actions - true`() = selfCancelingTest { + setUsers(2) + + val isVisible = mutableListOf() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } + + assertThat(isVisible.last()).isTrue() + job.cancel() + } + + @Test + fun `isOpenMenuButtonVisible - no actions - false`() = selfCancelingTest { + val userInfos = setUsers(2) + userRepository.setSelectedUserInfo(userInfos[1]) + keyguardRepository.setKeyguardShowing(true) + whenever(manager.canAddMoreUsers(any())).thenReturn(false) + + val isVisible = mutableListOf() + val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) } + + assertThat(isVisible.last()).isFalse() + job.cancel() + } + + @Test + fun menu() = selfCancelingTest { + val isMenuVisible = mutableListOf() + val job = launch(testDispatcher) { underTest.isMenuVisible.toList(isMenuVisible) } + assertThat(isMenuVisible.last()).isFalse() + + underTest.onOpenMenuButtonClicked() + assertThat(isMenuVisible.last()).isTrue() + + underTest.onMenuClosed() + assertThat(isMenuVisible.last()).isFalse() + + job.cancel() + } + + @Test + fun `menu actions`() = selfCancelingTest { + setUsers(2) + val actions = mutableListOf>() + val job = launch(testDispatcher) { underTest.menu.toList(actions) } + + assertThat(actions.last().map { it.viewKey }) + .isEqualTo( listOf( - UserModel( - id = 0, - name = Text.Loaded("zero"), - image = USER_IMAGE, - isSelected = true, - isSelectable = true, - isGuest = false, - ), - UserModel( - id = 1, - name = Text.Loaded("one"), - image = USER_IMAGE, - isSelected = false, - isSelectable = true, - isGuest = false, - ), - UserModel( - id = 2, - name = Text.Loaded("two"), - image = USER_IMAGE, - isSelected = false, - isSelectable = false, - isGuest = false, - ), + UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), + UserActionModel.ADD_USER.ordinal.toLong(), + UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), + UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), ) ) - var userViewModels: List? = null - val job = underTest.users.onEach { userViewModels = it }.launchIn(this) - - assertThat(userViewModels).hasSize(3) - assertUserViewModel( - viewModel = userViewModels?.get(0), - viewKey = 0, - name = "zero", - isSelectionMarkerVisible = true, - alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA, - isClickable = true, - ) - assertUserViewModel( - viewModel = userViewModels?.get(1), - viewKey = 1, - name = "one", - isSelectionMarkerVisible = false, - alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA, - isClickable = true, - ) - assertUserViewModel( - viewModel = userViewModels?.get(2), - viewKey = 2, - name = "two", - isSelectionMarkerVisible = false, - alpha = LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_NOT_SELECTABLE_ALPHA, - isClickable = false, - ) - job.cancel() - } + job.cancel() + } @Test - fun `maximumUserColumns - few users`() = - runBlocking(IMMEDIATE) { - setUsers(count = 2) - var value: Int? = null - val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this) + fun `isFinishRequested - finishes when user is switched`() = selfCancelingTest { + val userInfos = setUsers(count = 2) + val isFinishRequested = mutableListOf() + val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() - assertThat(value).isEqualTo(4) - job.cancel() - } + userRepository.setSelectedUserInfo(userInfos[1]) + + assertThat(isFinishRequested.last()).isTrue() + + job.cancel() + } @Test - fun `maximumUserColumns - many users`() = - runBlocking(IMMEDIATE) { - setUsers(count = 5) - var value: Int? = null - val job = underTest.maximumUserColumns.onEach { value = it }.launchIn(this) + fun `isFinishRequested - finishes when the screen turns off`() = selfCancelingTest { + setUsers(count = 2) + powerRepository.setInteractive(true) + val isFinishRequested = mutableListOf() + val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() - assertThat(value).isEqualTo(3) - job.cancel() - } + powerRepository.setInteractive(false) + + assertThat(isFinishRequested.last()).isTrue() + + job.cancel() + } @Test - fun `isOpenMenuButtonVisible - has actions - true`() = - runBlocking(IMMEDIATE) { - userRepository.setActions(UserActionModel.values().toList()) + fun `isFinishRequested - finishes when cancel button is clicked`() = selfCancelingTest { + setUsers(count = 2) + powerRepository.setInteractive(true) + val isFinishRequested = mutableListOf() + val job = launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) } + assertThat(isFinishRequested.last()).isFalse() - var isVisible: Boolean? = null - val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this) + underTest.onCancelButtonClicked() - assertThat(isVisible).isTrue() - job.cancel() - } + assertThat(isFinishRequested.last()).isTrue() - @Test - fun `isOpenMenuButtonVisible - no actions - false`() = - runBlocking(IMMEDIATE) { - userRepository.setActions(emptyList()) + underTest.onFinished() - var isVisible: Boolean? = null - val job = underTest.isOpenMenuButtonVisible.onEach { isVisible = it }.launchIn(this) + assertThat(isFinishRequested.last()).isFalse() - assertThat(isVisible).isFalse() - job.cancel() - } + job.cancel() + } - @Test - fun menu() = - runBlocking(IMMEDIATE) { - userRepository.setActions(UserActionModel.values().toList()) - var isMenuVisible: Boolean? = null - val job = underTest.isMenuVisible.onEach { isMenuVisible = it }.launchIn(this) - assertThat(isMenuVisible).isFalse() - - underTest.onOpenMenuButtonClicked() - assertThat(isMenuVisible).isTrue() - - underTest.onMenuClosed() - assertThat(isMenuVisible).isFalse() - - job.cancel() - } - - @Test - fun `menu actions`() = - runBlocking(IMMEDIATE) { - userRepository.setActions(UserActionModel.values().toList()) - var actions: List? = null - val job = underTest.menu.onEach { actions = it }.launchIn(this) - - assertThat(actions?.map { it.viewKey }) - .isEqualTo( - listOf( - UserActionModel.ENTER_GUEST_MODE.ordinal.toLong(), - UserActionModel.ADD_USER.ordinal.toLong(), - UserActionModel.ADD_SUPERVISED_USER.ordinal.toLong(), - UserActionModel.NAVIGATE_TO_USER_MANAGEMENT.ordinal.toLong(), - ) - ) - - job.cancel() - } - - @Test - fun `isFinishRequested - finishes when user is switched`() = - runBlocking(IMMEDIATE) { - setUsers(count = 2) - var isFinishRequested: Boolean? = null - val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this) - assertThat(isFinishRequested).isFalse() - - userRepository.setSelectedUser(1) - yield() - assertThat(isFinishRequested).isTrue() - - job.cancel() - } - - @Test - fun `isFinishRequested - finishes when the screen turns off`() = - runBlocking(IMMEDIATE) { - setUsers(count = 2) - powerRepository.setInteractive(true) - var isFinishRequested: Boolean? = null - val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this) - assertThat(isFinishRequested).isFalse() - - powerRepository.setInteractive(false) - yield() - assertThat(isFinishRequested).isTrue() - - job.cancel() - } - - @Test - fun `isFinishRequested - finishes when cancel button is clicked`() = - runBlocking(IMMEDIATE) { - setUsers(count = 2) - powerRepository.setInteractive(true) - var isFinishRequested: Boolean? = null - val job = underTest.isFinishRequested.onEach { isFinishRequested = it }.launchIn(this) - assertThat(isFinishRequested).isFalse() - - underTest.onCancelButtonClicked() - yield() - assertThat(isFinishRequested).isTrue() - - underTest.onFinished() - yield() - assertThat(isFinishRequested).isFalse() - - job.cancel() - } - - private suspend fun setUsers(count: Int) { - userRepository.setUsers( + private suspend fun setUsers(count: Int): List { + val userInfos = (0 until count).map { index -> - UserModel( - id = index, - name = Text.Loaded("$index"), - image = USER_IMAGE, - isSelected = index == 0, - isSelectable = true, - isGuest = false, + UserInfo( + /* id= */ index, + /* name= */ "$index", + /* iconPath= */ "", + /* flags= */ if (index == 0) { + // This is the primary user. + UserInfo.FLAG_PRIMARY or UserInfo.FLAG_ADMIN + } else { + // This isn't the primary user. + 0 + }, + UserManager.USER_TYPE_FULL_SYSTEM, ) } - ) + userRepository.setUserInfos(userInfos) + + if (userInfos.isNotEmpty()) { + userRepository.setSelectedUserInfo(userInfos[0]) + } + return userInfos } private fun assertUserViewModel( @@ -364,19 +380,25 @@ class UserSwitcherViewModelTest : SysuiTestCase() { viewKey: Int, name: String, isSelectionMarkerVisible: Boolean, - alpha: Float, - isClickable: Boolean, ) { checkNotNull(viewModel) assertThat(viewModel.viewKey).isEqualTo(viewKey) assertThat(viewModel.name).isEqualTo(Text.Loaded(name)) assertThat(viewModel.isSelectionMarkerVisible).isEqualTo(isSelectionMarkerVisible) - assertThat(viewModel.alpha).isEqualTo(alpha) - assertThat(viewModel.onClicked != null).isEqualTo(isClickable) + assertThat(viewModel.alpha) + .isEqualTo(LegacyUserUiHelper.USER_SWITCHER_USER_VIEW_SELECTABLE_ALPHA) + assertThat(viewModel.onClicked).isNotNull() } + private fun selfCancelingTest( + block: suspend TestScope.() -> Unit, + ): TestResult = + testScope.runTest { + block() + injectedScope.coroutineContext[Job.Key]?.cancelAndJoin() + } + companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - private val USER_IMAGE = mock() + private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package" } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 4df8aa42ea2f4..b7c8cbf40bea4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -20,26 +20,15 @@ package com.android.systemui.user.data.repository import android.content.pm.UserInfo import android.os.UserHandle import com.android.systemui.user.data.model.UserSwitcherSettingsModel -import com.android.systemui.user.shared.model.UserActionModel -import com.android.systemui.user.shared.model.UserModel import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map import kotlinx.coroutines.yield class FakeUserRepository : UserRepository { - private val _users = MutableStateFlow>(emptyList()) - override val users: Flow> = _users.asStateFlow() - override val selectedUser: Flow = - users.map { models -> models.first { model -> model.isSelected } } - - private val _actions = MutableStateFlow>(emptyList()) - override val actions: Flow> = _actions.asStateFlow() - private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel()) override val userSwitcherSettings: Flow = _userSwitcherSettings.asStateFlow() @@ -52,9 +41,6 @@ class FakeUserRepository : UserRepository { override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM - private val _isActionableWhenLocked = MutableStateFlow(false) - override val isActionableWhenLocked: Flow = _isActionableWhenLocked.asStateFlow() - private var _isGuestUserAutoCreated: Boolean = false override val isGuestUserAutoCreated: Boolean get() = _isGuestUserAutoCreated @@ -100,35 +86,6 @@ class FakeUserRepository : UserRepository { yield() } - fun setUsers(models: List) { - _users.value = models - } - - suspend fun setSelectedUser(userId: Int) { - check(_users.value.find { it.id == userId } != null) { - "Cannot select a user with ID $userId - no user with that ID found!" - } - - setUsers( - _users.value.map { model -> - when { - model.isSelected && model.id != userId -> model.copy(isSelected = false) - !model.isSelected && model.id == userId -> model.copy(isSelected = true) - else -> model - } - } - ) - yield() - } - - fun setActions(models: List) { - _actions.value = models - } - - fun setActionableWhenLocked(value: Boolean) { - _isActionableWhenLocked.value = value - } - fun setGuestUserAutoCreated(value: Boolean) { _isGuestUserAutoCreated = value }