UserInteractor changes to not depend on USC
Makes the UserInteractor no longer depend on UserSwitcherController, based on a flag. Bug: 246631653 Test: tests included, also manually tested with followup CL(s) Change-Id: Ia8783d461c5366dc437fad3308b6a776704ffad3
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
|
||||
/** Utilities related to user management actions. */
|
||||
object UserActionsUtil {
|
||||
|
||||
/** Returns `true` if it's possible to add a guest user to the device; `false` otherwise. */
|
||||
fun canCreateGuest(
|
||||
manager: UserManager,
|
||||
repository: UserRepository,
|
||||
isUserSwitcherEnabled: Boolean,
|
||||
isAddUsersFromLockScreenEnabled: Boolean,
|
||||
): Boolean {
|
||||
if (!isUserSwitcherEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
return currentUserCanCreateUsers(manager, repository) ||
|
||||
anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
|
||||
}
|
||||
|
||||
/** Returns `true` if it's possible to add a user to the device; `false` otherwise. */
|
||||
fun canCreateUser(
|
||||
manager: UserManager,
|
||||
repository: UserRepository,
|
||||
isUserSwitcherEnabled: Boolean,
|
||||
isAddUsersFromLockScreenEnabled: Boolean,
|
||||
): Boolean {
|
||||
if (!isUserSwitcherEnabled) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
!currentUserCanCreateUsers(manager, repository) &&
|
||||
!anyoneCanCreateUsers(manager, isAddUsersFromLockScreenEnabled)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return manager.canAddMoreUsers(UserManager.USER_TYPE_FULL_SECONDARY)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if it's possible to add a supervised user to the device; `false` otherwise.
|
||||
*/
|
||||
fun canCreateSupervisedUser(
|
||||
manager: UserManager,
|
||||
repository: UserRepository,
|
||||
isUserSwitcherEnabled: Boolean,
|
||||
isAddUsersFromLockScreenEnabled: Boolean,
|
||||
supervisedUserPackageName: String?
|
||||
): Boolean {
|
||||
if (supervisedUserPackageName.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return canCreateUser(
|
||||
manager,
|
||||
repository,
|
||||
isUserSwitcherEnabled,
|
||||
isAddUsersFromLockScreenEnabled
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the current user is allowed to add users to the device; `false` otherwise.
|
||||
*/
|
||||
private fun currentUserCanCreateUsers(
|
||||
manager: UserManager,
|
||||
repository: UserRepository,
|
||||
): Boolean {
|
||||
val currentUser = repository.getSelectedUserInfo()
|
||||
if (!currentUser.isAdmin && currentUser.id != UserHandle.USER_SYSTEM) {
|
||||
return false
|
||||
}
|
||||
|
||||
return systemCanCreateUsers(manager)
|
||||
}
|
||||
|
||||
/** Returns `true` if the system can add users to the device; `false` otherwise. */
|
||||
private fun systemCanCreateUsers(
|
||||
manager: UserManager,
|
||||
): Boolean {
|
||||
return !manager.hasBaseUserRestriction(UserManager.DISALLOW_ADD_USER, UserHandle.SYSTEM)
|
||||
}
|
||||
|
||||
/** Returns `true` if it's allowed to add users to the device at all; `false` otherwise. */
|
||||
private fun anyoneCanCreateUsers(
|
||||
manager: UserManager,
|
||||
isAddUsersFromLockScreenEnabled: Boolean,
|
||||
): Boolean {
|
||||
return systemCanCreateUsers(manager) && isAddUsersFromLockScreenEnabled
|
||||
}
|
||||
}
|
||||
@@ -17,94 +17,573 @@
|
||||
|
||||
package com.android.systemui.user.domain.interactor
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.UserIdInt
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.UserInfo
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.RemoteException
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import com.android.internal.util.UserIcons
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SystemUISecondaryUserService
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
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.statusbar.policy.UserSwitcherController
|
||||
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
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.kotlin.pairwise
|
||||
import java.util.Collections
|
||||
import java.util.WeakHashMap
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
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
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** Encapsulates business logic to interact with user data and systems. */
|
||||
@SysUISingleton
|
||||
class UserInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
repository: UserRepository,
|
||||
@Application private val applicationContext: Context,
|
||||
private val repository: UserRepository,
|
||||
private val controller: UserSwitcherController,
|
||||
private val activityStarter: ActivityStarter,
|
||||
keyguardInteractor: KeyguardInteractor,
|
||||
private val keyguardInteractor: KeyguardInteractor,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val manager: UserManager,
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
telephonyInteractor: TelephonyInteractor,
|
||||
broadcastDispatcher: BroadcastDispatcher,
|
||||
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val activityManager: ActivityManager,
|
||||
private val refreshUsersScheduler: RefreshUsersScheduler,
|
||||
private val guestUserInteractor: GuestUserInteractor,
|
||||
) {
|
||||
/**
|
||||
* Defines interface for classes that can be notified when the state of users on the device is
|
||||
* changed.
|
||||
*/
|
||||
fun interface UserCallback {
|
||||
/** Notifies that the state of users on the device has changed. */
|
||||
fun onUserStateChanged()
|
||||
}
|
||||
|
||||
private val isNewImpl: Boolean
|
||||
get() = featureFlags.isEnabled(Flags.REFACTORED_USER_SWITCHER_CONTROLLER)
|
||||
|
||||
private val supervisedUserPackageName: String?
|
||||
get() =
|
||||
applicationContext.getString(
|
||||
com.android.internal.R.string.config_supervisedUserCreationPackage
|
||||
)
|
||||
|
||||
private val callbacks = Collections.newSetFromMap(WeakHashMap<UserCallback, Boolean>())
|
||||
|
||||
/** List of current on-device users to select from. */
|
||||
val users: Flow<List<UserModel>> = repository.users
|
||||
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
|
||||
}
|
||||
|
||||
/** The currently-selected user. */
|
||||
val selectedUser: Flow<UserModel> = repository.selectedUser
|
||||
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,
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
repository.selectedUser
|
||||
}
|
||||
|
||||
/** List of user-switcher related actions that are available. */
|
||||
val actions: Flow<List<UserActionModel>> =
|
||||
combine(
|
||||
repository.isActionableWhenLocked,
|
||||
keyguardInteractor.isKeyguardShowing,
|
||||
) { isActionableWhenLocked, isLocked ->
|
||||
isActionableWhenLocked || !isLocked
|
||||
}
|
||||
.flatMapLatest { isActionable ->
|
||||
if (isActionable) {
|
||||
repository.actions.map { actions ->
|
||||
actions +
|
||||
if (actions.isNotEmpty()) {
|
||||
// If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT because
|
||||
// that's a user
|
||||
// switcher specific action that is not known to the our data source
|
||||
// or other
|
||||
// features.
|
||||
listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
|
||||
} else {
|
||||
// If no actions, don't add the navigate action.
|
||||
emptyList()
|
||||
}
|
||||
val actions: Flow<List<UserActionModel>>
|
||||
get() =
|
||||
if (isNewImpl) {
|
||||
combine(
|
||||
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.
|
||||
return@buildList
|
||||
}
|
||||
|
||||
if (
|
||||
UserActionsUtil.canCreateUser(
|
||||
manager,
|
||||
repository,
|
||||
settings.isUserSwitcherEnabled,
|
||||
settings.isAddUsersFromLockscreen,
|
||||
)
|
||||
) {
|
||||
add(UserActionModel.ADD_USER)
|
||||
}
|
||||
|
||||
if (
|
||||
UserActionsUtil.canCreateSupervisedUser(
|
||||
manager,
|
||||
repository,
|
||||
settings.isUserSwitcherEnabled,
|
||||
settings.isAddUsersFromLockscreen,
|
||||
supervisedUserPackageName,
|
||||
)
|
||||
) {
|
||||
add(UserActionModel.ADD_SUPERVISED_USER)
|
||||
}
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
} else {
|
||||
combine(
|
||||
repository.isActionableWhenLocked,
|
||||
keyguardInteractor.isKeyguardShowing,
|
||||
) { isActionableWhenLocked, isLocked ->
|
||||
isActionableWhenLocked || !isLocked
|
||||
}
|
||||
.flatMapLatest { isActionable ->
|
||||
if (isActionable) {
|
||||
repository.actions.map { actions ->
|
||||
actions +
|
||||
if (actions.isNotEmpty()) {
|
||||
// If we have actions, we add NAVIGATE_TO_USER_MANAGEMENT
|
||||
// because that's a user switcher specific action that is
|
||||
// not known to the our data source or other features.
|
||||
listOf(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
|
||||
} else {
|
||||
// If no actions, don't add the navigate action.
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
} 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the device is configured to always have a guest user available. */
|
||||
val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
|
||||
val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated
|
||||
|
||||
/** Whether the guest user is currently being reset. */
|
||||
val isGuestUserResetting: Boolean = repository.isGuestUserResetting
|
||||
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
|
||||
|
||||
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
|
||||
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
|
||||
|
||||
private val _dialogDismissRequests = MutableStateFlow<Unit?>(null)
|
||||
val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow()
|
||||
|
||||
val isSimpleUserSwitcher: Boolean
|
||||
get() =
|
||||
if (isNewImpl) {
|
||||
repository.isSimpleUserSwitcher()
|
||||
} else {
|
||||
error("Not supported in the old implementation!")
|
||||
}
|
||||
|
||||
fun addCallback(callback: UserCallback) {
|
||||
callbacks.add(callback)
|
||||
}
|
||||
|
||||
fun removeCallback(callback: UserCallback) {
|
||||
callbacks.remove(callback)
|
||||
}
|
||||
|
||||
fun onDialogShown() {
|
||||
_dialogShowRequests.value = null
|
||||
}
|
||||
|
||||
fun onDialogDismissed() {
|
||||
_dialogDismissRequests.value = null
|
||||
}
|
||||
|
||||
private fun showDialog(request: ShowDialogRequestModel) {
|
||||
_dialogShowRequests.value = request
|
||||
}
|
||||
|
||||
private fun dismissDialog() {
|
||||
_dialogDismissRequests.value = Unit
|
||||
}
|
||||
|
||||
init {
|
||||
if (isNewImpl) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDeviceBootCompleted() {
|
||||
guestUserInteractor.onDeviceBootCompleted()
|
||||
}
|
||||
|
||||
/** Switches to the user with the given user ID. */
|
||||
fun selectUser(
|
||||
userId: Int,
|
||||
newlySelectedUserId: Int,
|
||||
) {
|
||||
controller.onUserSelected(userId, /* 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,
|
||||
)
|
||||
)
|
||||
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,
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
switchUser(newlySelectedUserId)
|
||||
} else {
|
||||
controller.onUserSelected(newlySelectedUserId, /* dialogShower= */ null)
|
||||
}
|
||||
}
|
||||
|
||||
/** Executes the given action. */
|
||||
fun executeAction(action: UserActionModel) {
|
||||
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,
|
||||
)
|
||||
if (isNewImpl) {
|
||||
when (action) {
|
||||
UserActionModel.ENTER_GUEST_MODE ->
|
||||
guestUserInteractor.createAndSwitchTo(
|
||||
this::showDialog,
|
||||
this::dismissDialog,
|
||||
this::selectUser,
|
||||
)
|
||||
UserActionModel.ADD_USER -> {
|
||||
val currentUser = repository.getSelectedUserInfo()
|
||||
showDialog(
|
||||
ShowDialogRequestModel.ShowAddUserDialog(
|
||||
userHandle = currentUser.userHandle,
|
||||
isKeyguardShowing = keyguardInteractor.isKeyguardShowing(),
|
||||
showEphemeralMessage = currentUser.isGuest && currentUser.isEphemeral,
|
||||
)
|
||||
)
|
||||
}
|
||||
UserActionModel.ADD_SUPERVISED_USER ->
|
||||
activityStarter.startActivity(
|
||||
Intent()
|
||||
.setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
|
||||
.setPackage(supervisedUserPackageName)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
/* dismissShade= */ false,
|
||||
)
|
||||
UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
|
||||
activityStarter.startActivity(
|
||||
Intent(Settings.ACTION_USER_SETTINGS),
|
||||
/* dismissShade= */ false,
|
||||
)
|
||||
}
|
||||
} 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exitGuestUser(
|
||||
@UserIdInt guestUserId: Int,
|
||||
@UserIdInt targetUserId: Int,
|
||||
forceRemoveGuestOnExit: Boolean,
|
||||
) {
|
||||
guestUserInteractor.exit(
|
||||
guestUserId = guestUserId,
|
||||
targetUserId = targetUserId,
|
||||
forceRemoveGuestOnExit = forceRemoveGuestOnExit,
|
||||
showDialog = this::showDialog,
|
||||
dismissDialog = this::dismissDialog,
|
||||
switchUser = this::switchUser,
|
||||
)
|
||||
}
|
||||
|
||||
private fun switchUser(userId: Int) {
|
||||
// TODO(b/246631653): track jank and lantecy like in the old impl.
|
||||
refreshUsersScheduler.pause()
|
||||
try {
|
||||
activityManager.switchUser(userId)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(TAG, "Couldn't switch user.", e)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun onBroadcastReceived(
|
||||
intent: Intent,
|
||||
previousUserInfo: UserInfo?,
|
||||
) {
|
||||
val shouldRefreshAllUsers =
|
||||
when (intent.action) {
|
||||
Intent.ACTION_USER_SWITCHED -> {
|
||||
dismissDialog()
|
||||
val selectedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
|
||||
if (previousUserInfo?.id != selectedUserId) {
|
||||
callbacks.forEach { it.onUserStateChanged() }
|
||||
restartSecondaryService(selectedUserId)
|
||||
}
|
||||
if (guestUserInteractor.isGuestUserAutoCreated) {
|
||||
guestUserInteractor.guaranteePresent()
|
||||
}
|
||||
true
|
||||
}
|
||||
Intent.ACTION_USER_INFO_CHANGED -> true
|
||||
Intent.ACTION_USER_UNLOCKED -> {
|
||||
// If we unlocked the system user, we should refresh all users.
|
||||
intent.getIntExtra(
|
||||
Intent.EXTRA_USER_HANDLE,
|
||||
UserHandle.USER_NULL,
|
||||
) == UserHandle.USER_SYSTEM
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
|
||||
if (shouldRefreshAllUsers) {
|
||||
refreshUsersScheduler.unpauseAndRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
private fun restartSecondaryService(@UserIdInt userId: Int) {
|
||||
val intent = Intent(applicationContext, SystemUISecondaryUserService::class.java)
|
||||
// Disconnect from the old secondary user's service
|
||||
val secondaryUserId = repository.secondaryUserId
|
||||
if (secondaryUserId != UserHandle.USER_NULL) {
|
||||
applicationContext.stopServiceAsUser(
|
||||
intent,
|
||||
UserHandle.of(secondaryUserId),
|
||||
)
|
||||
repository.secondaryUserId = UserHandle.USER_NULL
|
||||
}
|
||||
|
||||
// Connect to the new secondary user's service (purely to ensure that a persistent
|
||||
// SystemUI application is created for that user)
|
||||
if (userId != UserHandle.USER_SYSTEM) {
|
||||
applicationContext.startServiceAsUser(
|
||||
intent,
|
||||
UserHandle.of(userId),
|
||||
)
|
||||
repository.secondaryUserId = userId
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun toUserModels(
|
||||
userInfos: List<UserInfo>,
|
||||
selectedUserId: Int,
|
||||
isUserSwitcherEnabled: Boolean,
|
||||
): List<UserModel> {
|
||||
val canSwitchUsers = canSwitchUsers(selectedUserId)
|
||||
|
||||
return userInfos
|
||||
// The guest user should go in the last position.
|
||||
.sortedBy { it.isGuest }
|
||||
.mapNotNull { userInfo ->
|
||||
toUserModel(
|
||||
userInfo = userInfo,
|
||||
selectedUserId = selectedUserId,
|
||||
canSwitchUsers = canSwitchUsers,
|
||||
isUserSwitcherEnabled = isUserSwitcherEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun toUserModel(
|
||||
userInfo: UserInfo,
|
||||
selectedUserId: Int,
|
||||
canSwitchUsers: Boolean,
|
||||
isUserSwitcherEnabled: Boolean,
|
||||
): UserModel? {
|
||||
val userId = userInfo.id
|
||||
val isSelected = userId == selectedUserId
|
||||
|
||||
return when {
|
||||
// When the user switcher is not enabled in settings, we only show the primary user.
|
||||
!isUserSwitcherEnabled && !userInfo.isPrimary -> null
|
||||
|
||||
// We avoid showing disabled users.
|
||||
!userInfo.isEnabled -> null
|
||||
userInfo.isGuest ->
|
||||
UserModel(
|
||||
id = userId,
|
||||
name = Text.Loaded(userInfo.name),
|
||||
image =
|
||||
getUserImage(
|
||||
isGuest = true,
|
||||
userId = userId,
|
||||
),
|
||||
isSelected = isSelected,
|
||||
isSelectable = canSwitchUsers,
|
||||
isGuest = true,
|
||||
)
|
||||
userInfo.supportsSwitchToByUser() ->
|
||||
UserModel(
|
||||
id = userId,
|
||||
name = Text.Loaded(userInfo.name),
|
||||
image =
|
||||
getUserImage(
|
||||
isGuest = false,
|
||||
userId = userId,
|
||||
),
|
||||
isSelected = isSelected,
|
||||
isSelectable = canSwitchUsers || isSelected,
|
||||
isGuest = false,
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun canSwitchUsers(selectedUserId: Int): Boolean {
|
||||
return withContext(backgroundDispatcher) {
|
||||
manager.getUserSwitchability(UserHandle.of(selectedUserId))
|
||||
} == UserManager.SWITCHABILITY_STATUS_OK
|
||||
}
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
private suspend fun getUserImage(
|
||||
isGuest: Boolean,
|
||||
userId: Int,
|
||||
): Drawable {
|
||||
if (isGuest) {
|
||||
return checkNotNull(applicationContext.getDrawable(R.drawable.ic_account_circle))
|
||||
}
|
||||
|
||||
// TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
|
||||
// TODO(b/246631653): downscale the bitmaps to R.dimen.max_avatar_size if requested.
|
||||
val userIcon = withContext(backgroundDispatcher) { manager.getUserIcon(userId) }
|
||||
if (userIcon != null) {
|
||||
return BitmapDrawable(userIcon)
|
||||
}
|
||||
|
||||
return UserIcons.getDefaultUserIcon(
|
||||
applicationContext.resources,
|
||||
userId,
|
||||
/* light= */ false
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UserInteractor"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* 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.user.data.model.UserSwitcherSettingsModel
|
||||
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 org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
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(
|
||||
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 `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,
|
||||
)
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `actions - device locked - only guest action 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))
|
||||
|
||||
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)
|
||||
|
||||
underTest.executeAction(UserActionModel.ADD_USER)
|
||||
assertThat(dialogRequest)
|
||||
.isEqualTo(
|
||||
ShowDialogRequestModel.ShowAddUserDialog(
|
||||
userHandle = userInfos[0].userHandle,
|
||||
isKeyguardShowing = false,
|
||||
showEphemeralMessage = false,
|
||||
)
|
||||
)
|
||||
|
||||
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(false))
|
||||
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(false))
|
||||
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)
|
||||
|
||||
assertThat(dialogRequest)
|
||||
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
|
||||
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)
|
||||
|
||||
assertThat(dialogRequest)
|
||||
.isInstanceOf(ShowDialogRequestModel.ShowExitGuestDialog::class.java)
|
||||
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)
|
||||
|
||||
assertThat(dialogRequest).isNull()
|
||||
verify(activityManager).switchUser(userInfos[1].id)
|
||||
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)
|
||||
}
|
||||
|
||||
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 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
|
||||
} 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 = mock()
|
||||
private val GUEST_ICON: Drawable = mock()
|
||||
private val SUPERVISED_USER_CREATION_APP_PACKAGE = "supervisedUserCreation"
|
||||
}
|
||||
}
|
||||
@@ -17,51 +17,60 @@
|
||||
|
||||
package com.android.systemui.user.domain.interactor
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import android.app.ActivityManager
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.os.UserManager
|
||||
import com.android.internal.logging.UiEventLogger
|
||||
import com.android.systemui.SysuiTestCase
|
||||
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.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.repository.FakeUserRepository
|
||||
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 kotlinx.coroutines.test.TestCoroutineScope
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.anyBoolean
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class UserInteractorTest : SysuiTestCase() {
|
||||
abstract class UserInteractorTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var controller: UserSwitcherController
|
||||
@Mock private lateinit var activityStarter: ActivityStarter
|
||||
@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
|
||||
|
||||
private lateinit var underTest: UserInteractor
|
||||
protected lateinit var underTest: UserInteractor
|
||||
|
||||
private lateinit var userRepository: FakeUserRepository
|
||||
private lateinit var keyguardRepository: FakeKeyguardRepository
|
||||
protected lateinit var userRepository: FakeUserRepository
|
||||
protected lateinit var keyguardRepository: FakeKeyguardRepository
|
||||
protected lateinit var telephonyRepository: FakeTelephonyRepository
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
abstract fun isRefactored(): Boolean
|
||||
|
||||
open fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
userRepository = FakeUserRepository()
|
||||
keyguardRepository = FakeKeyguardRepository()
|
||||
telephonyRepository = FakeTelephonyRepository()
|
||||
val applicationScope = TestCoroutineScope()
|
||||
val refreshUsersScheduler =
|
||||
RefreshUsersScheduler(
|
||||
applicationScope = applicationScope,
|
||||
mainDispatcher = IMMEDIATE,
|
||||
repository = userRepository,
|
||||
)
|
||||
underTest =
|
||||
UserInteractor(
|
||||
applicationContext = context,
|
||||
repository = userRepository,
|
||||
controller = controller,
|
||||
activityStarter = activityStarter,
|
||||
@@ -69,142 +78,34 @@ class UserInteractorTest : SysuiTestCase() {
|
||||
KeyguardInteractor(
|
||||
repository = keyguardRepository,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@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) {
|
||||
userRepository.setActions(
|
||||
listOf(
|
||||
UserActionModel.ENTER_GUEST_MODE,
|
||||
UserActionModel.ADD_USER,
|
||||
UserActionModel.ADD_SUPERVISED_USER,
|
||||
)
|
||||
)
|
||||
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,
|
||||
featureFlags =
|
||||
FakeFeatureFlags().apply {
|
||||
set(Flags.REFACTORED_USER_SWITCHER_CONTROLLER, isRefactored())
|
||||
},
|
||||
manager = manager,
|
||||
applicationScope = applicationScope,
|
||||
telephonyInteractor =
|
||||
TelephonyInteractor(
|
||||
repository = telephonyRepository,
|
||||
),
|
||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
activityManager = activityManager,
|
||||
refreshUsersScheduler = refreshUsersScheduler,
|
||||
guestUserInteractor =
|
||||
GuestUserInteractor(
|
||||
applicationContext = context,
|
||||
applicationScope = applicationScope,
|
||||
mainDispatcher = IMMEDIATE,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
manager = manager,
|
||||
repository = userRepository,
|
||||
deviceProvisionedController = deviceProvisionedController,
|
||||
devicePolicyManager = devicePolicyManager,
|
||||
refreshUsersScheduler = refreshUsersScheduler,
|
||||
uiEventLogger = uiEventLogger,
|
||||
)
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `actions - actionable when locked and not locked`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
userRepository.setActions(
|
||||
listOf(
|
||||
UserActionModel.ENTER_GUEST_MODE,
|
||||
UserActionModel.ADD_USER,
|
||||
UserActionModel.ADD_SUPERVISED_USER,
|
||||
)
|
||||
)
|
||||
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) {
|
||||
userRepository.setActions(
|
||||
listOf(
|
||||
UserActionModel.ENTER_GUEST_MODE,
|
||||
UserActionModel.ADD_USER,
|
||||
UserActionModel.ADD_SUPERVISED_USER,
|
||||
)
|
||||
)
|
||||
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())
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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) {
|
||||
userRepository.setActions(
|
||||
listOf(
|
||||
UserActionModel.ENTER_GUEST_MODE,
|
||||
UserActionModel.ADD_USER,
|
||||
UserActionModel.ADD_SUPERVISED_USER,
|
||||
)
|
||||
)
|
||||
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) {
|
||||
userRepository.setActions(
|
||||
listOf(
|
||||
UserActionModel.ENTER_GUEST_MODE,
|
||||
UserActionModel.ADD_USER,
|
||||
UserActionModel.ADD_SUPERVISED_USER,
|
||||
)
|
||||
)
|
||||
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) {
|
||||
userRepository.setActions(
|
||||
listOf(
|
||||
UserActionModel.ENTER_GUEST_MODE,
|
||||
UserActionModel.ADD_USER,
|
||||
UserActionModel.ADD_SUPERVISED_USER,
|
||||
)
|
||||
)
|
||||
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())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
@@ -17,17 +17,28 @@
|
||||
|
||||
package com.android.systemui.user.ui.viewmodel
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.UserManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.UiEventLogger
|
||||
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.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
|
||||
@@ -38,6 +49,7 @@ 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.yield
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@@ -52,6 +64,11 @@ 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
|
||||
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
|
||||
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
|
||||
@Mock private lateinit var uiEventLogger: UiEventLogger
|
||||
|
||||
private lateinit var underTest: UserSwitcherViewModel
|
||||
|
||||
@@ -66,17 +83,51 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
|
||||
userRepository = FakeUserRepository()
|
||||
keyguardRepository = FakeKeyguardRepository()
|
||||
powerRepository = FakePowerRepository()
|
||||
val featureFlags = FakeFeatureFlags()
|
||||
featureFlags.set(Flags.REFACTORED_USER_SWITCHER_CONTROLLER, false)
|
||||
val scope = TestCoroutineScope()
|
||||
val refreshUsersScheduler =
|
||||
RefreshUsersScheduler(
|
||||
applicationScope = scope,
|
||||
mainDispatcher = IMMEDIATE,
|
||||
repository = userRepository,
|
||||
)
|
||||
underTest =
|
||||
UserSwitcherViewModel.Factory(
|
||||
userInteractor =
|
||||
UserInteractor(
|
||||
applicationContext = context,
|
||||
repository = userRepository,
|
||||
controller = controller,
|
||||
activityStarter = activityStarter,
|
||||
keyguardInteractor =
|
||||
KeyguardInteractor(
|
||||
repository = keyguardRepository,
|
||||
)
|
||||
),
|
||||
featureFlags = featureFlags,
|
||||
manager = manager,
|
||||
applicationScope = scope,
|
||||
telephonyInteractor =
|
||||
TelephonyInteractor(
|
||||
repository = FakeTelephonyRepository(),
|
||||
),
|
||||
broadcastDispatcher = fakeBroadcastDispatcher,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
activityManager = activityManager,
|
||||
refreshUsersScheduler = refreshUsersScheduler,
|
||||
guestUserInteractor =
|
||||
GuestUserInteractor(
|
||||
applicationContext = context,
|
||||
applicationScope = scope,
|
||||
mainDispatcher = IMMEDIATE,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
manager = manager,
|
||||
repository = userRepository,
|
||||
deviceProvisionedController = deviceProvisionedController,
|
||||
devicePolicyManager = devicePolicyManager,
|
||||
refreshUsersScheduler = refreshUsersScheduler,
|
||||
uiEventLogger = uiEventLogger,
|
||||
),
|
||||
),
|
||||
powerInteractor =
|
||||
PowerInteractor(
|
||||
|
||||
@@ -37,10 +37,18 @@ class FakeBroadcastDispatcher(
|
||||
dumpManager: DumpManager,
|
||||
logger: BroadcastDispatcherLogger,
|
||||
userTracker: UserTracker
|
||||
) : BroadcastDispatcher(
|
||||
context, looper, executor, dumpManager, logger, userTracker, PendingRemovalStore(logger)) {
|
||||
) :
|
||||
BroadcastDispatcher(
|
||||
context,
|
||||
looper,
|
||||
executor,
|
||||
dumpManager,
|
||||
logger,
|
||||
userTracker,
|
||||
PendingRemovalStore(logger)
|
||||
) {
|
||||
|
||||
private val registeredReceivers = ArraySet<BroadcastReceiver>()
|
||||
val registeredReceivers = ArraySet<BroadcastReceiver>()
|
||||
|
||||
override fun registerReceiverWithHandler(
|
||||
receiver: BroadcastReceiver,
|
||||
@@ -78,4 +86,4 @@ class FakeBroadcastDispatcher(
|
||||
}
|
||||
registeredReceivers.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user