Merge "Cleans up old user switcher impl. and flags." into tm-qpr-dev
This commit is contained in:
@@ -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<UserViewModel> by viewModel.users.collectAsState(emptyList())
|
||||
val maxUserColumns: Int by viewModel.maximumUserColumns.collectAsState(1)
|
||||
val menuActions: List<UserActionViewModel> 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<UserViewModel>,
|
||||
maxUserColumns: Int,
|
||||
menuActions: List<UserActionViewModel>,
|
||||
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<UserViewModel>,
|
||||
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<UserActionViewModel>,
|
||||
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<UserActionViewModel>,
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
@@ -4285,7 +4284,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
}
|
||||
// TODO: Bring these out of CentralSurfaces.
|
||||
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
|
||||
mUserSwitcherController.onDensityOrFontScaleChanged();
|
||||
mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
|
||||
mHeadsUpManager.onDensityOrFontScaleChanged();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<UserInteractor>,
|
||||
private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
|
||||
private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
|
||||
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<UserSwitchCallback, UserInteractor.UserCallback>()
|
||||
|
||||
/** The current list of [UserRecord]. */
|
||||
val users: ArrayList<UserRecord>
|
||||
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<Boolean>
|
||||
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<BaseUserSwitcherAdapter>)
|
||||
fun addAdapter(adapter: WeakReference<BaseUserSwitcherAdapter>) {
|
||||
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<out String>) {
|
||||
userInteractor.dump(pw)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -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<UserSwitcherControllerOldImpl>,
|
||||
private val userInteractorLazy: Lazy<UserInteractor>,
|
||||
private val guestUserInteractorLazy: Lazy<GuestUserInteractor>,
|
||||
private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
|
||||
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<UserSwitcherController.UserSwitchCallback, UserInteractor.UserCallback>()
|
||||
|
||||
private fun notSupported(): Nothing {
|
||||
error("Not supported in the new implementation!")
|
||||
}
|
||||
|
||||
override val users: ArrayList<UserRecord>
|
||||
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<Boolean>
|
||||
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<BaseUserSwitcherAdapter>) {
|
||||
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<out String>) {
|
||||
if (useInteractor) {
|
||||
userInteractor.dump(pw)
|
||||
} else {
|
||||
_oldImpl.dump(pw, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<List<UserModel>>
|
||||
|
||||
/** The currently-selected user. */
|
||||
val selectedUser: Flow<UserModel>
|
||||
|
||||
/** List of available user-related actions. */
|
||||
val actions: Flow<List<UserActionModel>>
|
||||
|
||||
/** User switcher related settings. */
|
||||
val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
|
||||
|
||||
@@ -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<Boolean>
|
||||
|
||||
/** 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<UserSwitcherSettingsModel> =
|
||||
_userSwitcherSettings.asStateFlow().filterNotNull()
|
||||
@@ -150,58 +119,11 @@ constructor(
|
||||
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
|
||||
private set
|
||||
|
||||
private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
|
||||
fun send() {
|
||||
trySendWithFailureLogging(
|
||||
controller.users,
|
||||
TAG,
|
||||
)
|
||||
}
|
||||
|
||||
val callback = UserSwitcherController.UserSwitchCallback { send() }
|
||||
|
||||
controller.addUserSwitchCallback(callback)
|
||||
send()
|
||||
|
||||
awaitClose { controller.removeUserSwitchCallback(callback) }
|
||||
}
|
||||
|
||||
override val users: Flow<List<UserModel>> =
|
||||
userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
|
||||
|
||||
override val selectedUser: Flow<UserModel> =
|
||||
users.map { users -> users.first { user -> user.isSelected } }
|
||||
|
||||
override val actions: Flow<List<UserActionModel>> =
|
||||
userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
|
||||
|
||||
override val isActionableWhenLocked: Flow<Boolean> =
|
||||
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"
|
||||
|
||||
@@ -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<List<UserModel>>
|
||||
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<UserModel>
|
||||
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<List<UserActionModel>>
|
||||
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<ArrayList<UserRecord>> =
|
||||
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<UserRecord?> =
|
||||
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<Unit?> = _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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BroadcastSender>,
|
||||
private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
|
||||
private val interactor: Lazy<UserInteractor>,
|
||||
private val featureFlags: Lazy<FeatureFlags>,
|
||||
) : CoreStartable {
|
||||
|
||||
private var currentDialog: Dialog? = null
|
||||
|
||||
override fun start() {
|
||||
if (featureFlags.get().isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)) {
|
||||
return
|
||||
}
|
||||
|
||||
startHandlingDialogShowRequests()
|
||||
startHandlingDialogDismissRequests()
|
||||
}
|
||||
|
||||
@@ -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<List<UserViewModel>> =
|
||||
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 <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -224,7 +217,6 @@ private constructor(
|
||||
userInteractor = userInteractor,
|
||||
guestUserInteractor = guestUserInteractor,
|
||||
powerInteractor = powerInteractor,
|
||||
featureFlags = featureFlags,
|
||||
)
|
||||
as T
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ class BaseUserSwitcherAdapterTest : SysuiTestCase() {
|
||||
fun refresh() {
|
||||
underTest.refresh()
|
||||
|
||||
verify(controller).refreshUsers(UserHandle.USER_NULL)
|
||||
verify(controller).refreshUsers()
|
||||
}
|
||||
|
||||
private fun createUserRecord(
|
||||
|
||||
@@ -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<BroadcastReceiver>()
|
||||
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<Intent>()
|
||||
verify(activityStarter).startActivity(intentCaptor.capture(),
|
||||
eq(true)
|
||||
)
|
||||
Truth.assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_USER_SETTINGS)
|
||||
}
|
||||
}
|
||||
@@ -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<UserInfo>? = 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<UserInfo>? = 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<UserInfo> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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<UserInfo>? = 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<UserInfo>? = 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<UserInfo> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UserSwitcherController.UserSwitchCallback>
|
||||
|
||||
@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<UserModel>? = 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<UserActionModel>? = 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<UserModel>? = 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<UserModel>? = 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<UserModel>? = 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<UserActionModel>? = 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<UserActionModel>? = null
|
||||
val job = underTest.actions.onEach { value = it }.launchIn(this)
|
||||
|
||||
assertThat(value).isEqualTo(emptyList<UserActionModel>())
|
||||
|
||||
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<UserActionModel>? = null
|
||||
val job = underTest.actions.onEach { value = it }.launchIn(this)
|
||||
|
||||
assertThat(value).isEqualTo(emptyList<UserActionModel>())
|
||||
|
||||
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<UserActionModel>? = 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<UserActionModel>? = 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<Intent>()
|
||||
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<Intent>()
|
||||
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<ShowDialogRequestModel?>()
|
||||
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<UserModel>?,
|
||||
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<UserRecord>,
|
||||
userIds: List<Int>,
|
||||
selectedUserIndex: Int = 0,
|
||||
includeGuest: Boolean = false,
|
||||
expectedActions: List<UserActionModel> = emptyList(),
|
||||
) {
|
||||
assertThat(records.size >= userIds.size).isTrue()
|
||||
userIds.indices.forEach { userIndex ->
|
||||
val record = records[userIndex]
|
||||
assertThat(record.info).isNotNull()
|
||||
val isGuest = includeGuest && userIndex == userIds.size - 1
|
||||
assertRecordForUser(
|
||||
record = record,
|
||||
id = userIds[userIndex],
|
||||
hasPicture = !isGuest,
|
||||
isCurrent = userIndex == selectedUserIndex,
|
||||
isGuest = isGuest,
|
||||
isSwitchToEnabled = true,
|
||||
)
|
||||
}
|
||||
|
||||
assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
|
||||
(userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
|
||||
val record = records[actionIndex]
|
||||
assertThat(record.info).isNull()
|
||||
assertRecordForAction(
|
||||
record = record,
|
||||
type = expectedActions[actionIndex - userIds.size],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertRecordForUser(
|
||||
record: UserRecord?,
|
||||
id: Int? = null,
|
||||
hasPicture: Boolean = false,
|
||||
isCurrent: Boolean = false,
|
||||
isGuest: Boolean = false,
|
||||
isSwitchToEnabled: Boolean = false,
|
||||
) {
|
||||
checkNotNull(record)
|
||||
assertThat(record.info?.id).isEqualTo(id)
|
||||
assertThat(record.picture != null).isEqualTo(hasPicture)
|
||||
assertThat(record.isCurrent).isEqualTo(isCurrent)
|
||||
assertThat(record.isGuest).isEqualTo(isGuest)
|
||||
assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
|
||||
}
|
||||
|
||||
private fun assertRecordForAction(
|
||||
record: UserRecord,
|
||||
type: UserActionModel,
|
||||
) {
|
||||
assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
|
||||
assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
|
||||
assertThat(record.isAddSupervisedUser)
|
||||
.isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
|
||||
}
|
||||
|
||||
private fun createUserInfos(
|
||||
count: Int,
|
||||
includeGuest: Boolean,
|
||||
): List<UserInfo> {
|
||||
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"
|
||||
}
|
||||
}
|
||||
@@ -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<UserModel>? = 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<UserModel>? = 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<UserModel>? = 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<UserActionModel>? = 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<UserActionModel>? = null
|
||||
val job = underTest.actions.onEach { value = it }.launchIn(this)
|
||||
|
||||
assertThat(value).isEqualTo(emptyList<UserActionModel>())
|
||||
|
||||
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<UserActionModel>? = null
|
||||
val job = underTest.actions.onEach { value = it }.launchIn(this)
|
||||
|
||||
assertThat(value).isEqualTo(emptyList<UserActionModel>())
|
||||
|
||||
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<UserActionModel>? = 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<UserActionModel>? = 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<Intent>()
|
||||
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<Intent>()
|
||||
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<ShowDialogRequestModel?>()
|
||||
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<UserModel>?,
|
||||
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<UserRecord>,
|
||||
userIds: List<Int>,
|
||||
selectedUserIndex: Int = 0,
|
||||
includeGuest: Boolean = false,
|
||||
expectedActions: List<UserActionModel> = emptyList(),
|
||||
) {
|
||||
assertThat(records.size >= userIds.size).isTrue()
|
||||
userIds.indices.forEach { userIndex ->
|
||||
val record = records[userIndex]
|
||||
assertThat(record.info).isNotNull()
|
||||
val isGuest = includeGuest && userIndex == userIds.size - 1
|
||||
assertRecordForUser(
|
||||
record = record,
|
||||
id = userIds[userIndex],
|
||||
hasPicture = !isGuest,
|
||||
isCurrent = userIndex == selectedUserIndex,
|
||||
isGuest = isGuest,
|
||||
isSwitchToEnabled = true,
|
||||
)
|
||||
}
|
||||
|
||||
assertThat(records.size - userIds.size).isEqualTo(expectedActions.size)
|
||||
(userIds.size until userIds.size + expectedActions.size).forEach { actionIndex ->
|
||||
val record = records[actionIndex]
|
||||
assertThat(record.info).isNull()
|
||||
assertRecordForAction(
|
||||
record = record,
|
||||
type = expectedActions[actionIndex - userIds.size],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertRecordForUser(
|
||||
record: UserRecord?,
|
||||
id: Int? = null,
|
||||
hasPicture: Boolean = false,
|
||||
isCurrent: Boolean = false,
|
||||
isGuest: Boolean = false,
|
||||
isSwitchToEnabled: Boolean = false,
|
||||
) {
|
||||
checkNotNull(record)
|
||||
assertThat(record.info?.id).isEqualTo(id)
|
||||
assertThat(record.picture != null).isEqualTo(hasPicture)
|
||||
assertThat(record.isCurrent).isEqualTo(isCurrent)
|
||||
assertThat(record.isGuest).isEqualTo(isGuest)
|
||||
assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
|
||||
}
|
||||
|
||||
private fun assertRecordForAction(
|
||||
record: UserRecord,
|
||||
type: UserActionModel,
|
||||
) {
|
||||
assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
|
||||
assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
|
||||
assertThat(record.isAddSupervisedUser)
|
||||
.isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
|
||||
}
|
||||
|
||||
private fun createUserInfos(
|
||||
count: Int,
|
||||
includeGuest: Boolean,
|
||||
): List<UserInfo> {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<UserActionModel>? = 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<UserActionModel>? = 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<UserActionModel>? = 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<UserActionModel>? = 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
|
||||
}
|
||||
}
|
||||
@@ -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<List<UserViewModel>>()
|
||||
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<Int>()
|
||||
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<Int>()
|
||||
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<Boolean>()
|
||||
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<Boolean>()
|
||||
val job = launch(testDispatcher) { underTest.isOpenMenuButtonVisible.toList(isVisible) }
|
||||
|
||||
assertThat(isVisible.last()).isFalse()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun menu() = selfCancelingTest {
|
||||
val isMenuVisible = mutableListOf<Boolean>()
|
||||
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<List<UserActionViewModel>>()
|
||||
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<UserViewModel>? = 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<Boolean>()
|
||||
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<Boolean>()
|
||||
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<Boolean>()
|
||||
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<UserActionViewModel>? = 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<UserInfo> {
|
||||
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<Drawable>()
|
||||
private const val SUPERVISED_USER_CREATION_PACKAGE = "com.some.package"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<List<UserModel>>(emptyList())
|
||||
override val users: Flow<List<UserModel>> = _users.asStateFlow()
|
||||
override val selectedUser: Flow<UserModel> =
|
||||
users.map { models -> models.first { model -> model.isSelected } }
|
||||
|
||||
private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
|
||||
override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
|
||||
|
||||
private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel())
|
||||
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
|
||||
_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<Boolean> = _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<UserModel>) {
|
||||
_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<UserActionModel>) {
|
||||
_actions.value = models
|
||||
}
|
||||
|
||||
fun setActionableWhenLocked(value: Boolean) {
|
||||
_isActionableWhenLocked.value = value
|
||||
}
|
||||
|
||||
fun setGuestUserAutoCreated(value: Boolean) {
|
||||
_isGuestUserAutoCreated = value
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user