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:
Alejandro Nijamkin
2022-09-28 10:04:24 -07:00
parent 7c822664be
commit fbbb1a8bd5
7 changed files with 1503 additions and 208 deletions

View File

@@ -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
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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(

View File

@@ -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()
}
}
}