GuestUserInteractor.
Business logic surrounding guest user. Bug: 246631653 Test: included in CL Change-Id: I7363f64f93fff803ecabd3db4050fcdc9981aaaf
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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.annotation.UserIdInt
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.Context
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.RemoteException
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.util.Log
|
||||
import android.view.WindowManagerGlobal
|
||||
import android.widget.Toast
|
||||
import com.android.internal.logging.UiEventLogger
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.qs.QSUserSwitcherEvent
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController
|
||||
import com.android.systemui.user.data.repository.UserRepository
|
||||
import com.android.systemui.user.domain.model.ShowDialogRequestModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/** Encapsulates business logic to interact with guest user data and systems. */
|
||||
@SysUISingleton
|
||||
class GuestUserInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val applicationContext: Context,
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
@Main private val mainDispatcher: CoroutineDispatcher,
|
||||
@Background private val backgroundDispatcher: CoroutineDispatcher,
|
||||
private val manager: UserManager,
|
||||
private val repository: UserRepository,
|
||||
private val deviceProvisionedController: DeviceProvisionedController,
|
||||
private val devicePolicyManager: DevicePolicyManager,
|
||||
private val refreshUsersScheduler: RefreshUsersScheduler,
|
||||
private val uiEventLogger: UiEventLogger,
|
||||
) {
|
||||
/** Whether the device is configured to always have a guest user available. */
|
||||
val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
|
||||
|
||||
/** Whether the guest user is currently being reset. */
|
||||
val isGuestUserResetting: Boolean = repository.isGuestUserResetting
|
||||
|
||||
/** Notifies that the device has finished booting. */
|
||||
fun onDeviceBootCompleted() {
|
||||
applicationScope.launch {
|
||||
if (isDeviceAllowedToAddGuest()) {
|
||||
guaranteePresent()
|
||||
return@launch
|
||||
}
|
||||
|
||||
suspendCancellableCoroutine<Unit> { continuation ->
|
||||
val callback =
|
||||
object : DeviceProvisionedController.DeviceProvisionedListener {
|
||||
override fun onDeviceProvisionedChanged() {
|
||||
continuation.resumeWith(Result.success(Unit))
|
||||
deviceProvisionedController.removeCallback(this)
|
||||
}
|
||||
}
|
||||
|
||||
deviceProvisionedController.addCallback(callback)
|
||||
}
|
||||
|
||||
if (isDeviceAllowedToAddGuest()) {
|
||||
guaranteePresent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a guest user and switches to it. */
|
||||
fun createAndSwitchTo(
|
||||
showDialog: (ShowDialogRequestModel) -> Unit,
|
||||
dismissDialog: () -> Unit,
|
||||
selectUser: (userId: Int) -> Unit,
|
||||
) {
|
||||
applicationScope.launch {
|
||||
val newGuestUserId = create(showDialog, dismissDialog)
|
||||
if (newGuestUserId != UserHandle.USER_NULL) {
|
||||
selectUser(newGuestUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Exits the guest user, switching back to the last non-guest user or to the default user. */
|
||||
fun exit(
|
||||
@UserIdInt guestUserId: Int,
|
||||
@UserIdInt targetUserId: Int,
|
||||
forceRemoveGuestOnExit: Boolean,
|
||||
showDialog: (ShowDialogRequestModel) -> Unit,
|
||||
dismissDialog: () -> Unit,
|
||||
switchUser: (userId: Int) -> Unit,
|
||||
) {
|
||||
val currentUserInfo = repository.getSelectedUserInfo()
|
||||
if (currentUserInfo.id != guestUserId) {
|
||||
Log.w(
|
||||
TAG,
|
||||
"User requesting to start a new session ($guestUserId) is not current user" +
|
||||
" (${currentUserInfo.id})"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentUserInfo.isGuest) {
|
||||
Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
|
||||
return
|
||||
}
|
||||
|
||||
applicationScope.launch {
|
||||
var newUserId = UserHandle.USER_SYSTEM
|
||||
if (targetUserId == UserHandle.USER_NULL) {
|
||||
// When a target user is not specified switch to last non guest user:
|
||||
val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId
|
||||
if (lastSelectedNonGuestUserHandle != UserHandle.USER_SYSTEM) {
|
||||
val info =
|
||||
withContext(backgroundDispatcher) {
|
||||
manager.getUserInfo(lastSelectedNonGuestUserHandle)
|
||||
}
|
||||
if (info != null && info.isEnabled && info.supportsSwitchToByUser()) {
|
||||
newUserId = info.id
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newUserId = targetUserId
|
||||
}
|
||||
|
||||
if (currentUserInfo.isEphemeral || forceRemoveGuestOnExit) {
|
||||
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE)
|
||||
remove(currentUserInfo.id, newUserId, showDialog, dismissDialog, switchUser)
|
||||
} else {
|
||||
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH)
|
||||
switchUser(newUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarantees that the guest user is present on the device, creating it if needed and if allowed
|
||||
* to.
|
||||
*/
|
||||
suspend fun guaranteePresent() {
|
||||
if (!isDeviceAllowedToAddGuest()) {
|
||||
return
|
||||
}
|
||||
|
||||
val guestUser = withContext(backgroundDispatcher) { manager.findCurrentGuestUser() }
|
||||
if (guestUser == null) {
|
||||
scheduleCreation()
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes the guest user from the device. */
|
||||
private suspend fun remove(
|
||||
@UserIdInt guestUserId: Int,
|
||||
@UserIdInt targetUserId: Int,
|
||||
showDialog: (ShowDialogRequestModel) -> Unit,
|
||||
dismissDialog: () -> Unit,
|
||||
switchUser: (userId: Int) -> Unit,
|
||||
) {
|
||||
val currentUser: UserInfo = repository.getSelectedUserInfo()
|
||||
if (currentUser.id != guestUserId) {
|
||||
Log.w(
|
||||
TAG,
|
||||
"User requesting to start a new session ($guestUserId) is not current user" +
|
||||
" ($currentUser.id)"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentUser.isGuest) {
|
||||
Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest")
|
||||
return
|
||||
}
|
||||
|
||||
val marked =
|
||||
withContext(backgroundDispatcher) { manager.markGuestForDeletion(currentUser.id) }
|
||||
if (!marked) {
|
||||
Log.w(TAG, "Couldn't mark the guest for deletion for user $guestUserId")
|
||||
return
|
||||
}
|
||||
|
||||
if (targetUserId == UserHandle.USER_NULL) {
|
||||
// Create a new guest in the foreground, and then immediately switch to it
|
||||
val newGuestId = create(showDialog, dismissDialog)
|
||||
if (newGuestId == UserHandle.USER_NULL) {
|
||||
Log.e(TAG, "Could not create new guest, switching back to system user")
|
||||
switchUser(UserHandle.USER_SYSTEM)
|
||||
withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
|
||||
} catch (e: RemoteException) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Couldn't remove guest because ActivityManager or WindowManager is dead"
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switchUser(newGuestId)
|
||||
|
||||
withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
|
||||
} else {
|
||||
if (repository.isGuestUserAutoCreated) {
|
||||
repository.isGuestUserResetting = true
|
||||
}
|
||||
switchUser(targetUserId)
|
||||
manager.removeUser(currentUser.id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the guest user and adds it to the device.
|
||||
*
|
||||
* @param showDialog A function to invoke to show a dialog.
|
||||
* @param dismissDialog A function to invoke to dismiss a dialog.
|
||||
* @return The user ID of the newly-created guest user.
|
||||
*/
|
||||
private suspend fun create(
|
||||
showDialog: (ShowDialogRequestModel) -> Unit,
|
||||
dismissDialog: () -> Unit,
|
||||
): Int {
|
||||
return withContext(mainDispatcher) {
|
||||
showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
|
||||
val guestUserId = createInBackground()
|
||||
dismissDialog()
|
||||
if (guestUserId != UserHandle.USER_NULL) {
|
||||
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
com.android.settingslib.R.string.add_guest_failed,
|
||||
Toast.LENGTH_SHORT,
|
||||
)
|
||||
.show()
|
||||
}
|
||||
|
||||
guestUserId
|
||||
}
|
||||
}
|
||||
|
||||
/** Schedules the creation of the guest user. */
|
||||
private suspend fun scheduleCreation() {
|
||||
if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) {
|
||||
return
|
||||
}
|
||||
|
||||
withContext(backgroundDispatcher) {
|
||||
val newGuestUserId = createInBackground()
|
||||
repository.isGuestUserCreationScheduled.set(false)
|
||||
repository.isGuestUserResetting = false
|
||||
if (newGuestUserId == UserHandle.USER_NULL) {
|
||||
Log.w(TAG, "Could not create new guest while exiting existing guest")
|
||||
// Refresh users so that we still display "Guest" if
|
||||
// config_guestUserAutoCreated=true
|
||||
refreshUsersScheduler.refreshIfNotPaused()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a guest user and return its multi-user user ID.
|
||||
*
|
||||
* This method does not check if a guest already exists before it makes a call to [UserManager]
|
||||
* to create a new one.
|
||||
*
|
||||
* @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if
|
||||
* the guest couldn't be created.
|
||||
*/
|
||||
@UserIdInt
|
||||
private suspend fun createInBackground(): Int {
|
||||
return withContext(backgroundDispatcher) {
|
||||
try {
|
||||
val guestUser = manager.createGuest(applicationContext)
|
||||
if (guestUser != null) {
|
||||
guestUser.id
|
||||
} else {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Couldn't create guest, most likely because there already exists one!"
|
||||
)
|
||||
UserHandle.USER_NULL
|
||||
}
|
||||
} catch (e: UserManager.UserOperationException) {
|
||||
Log.e(TAG, "Couldn't create guest user!", e)
|
||||
UserHandle.USER_NULL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDeviceAllowedToAddGuest(): Boolean {
|
||||
return deviceProvisionedController.isDeviceProvisioned &&
|
||||
!devicePolicyManager.isDeviceManaged
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GuestUserInteractor"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.model
|
||||
|
||||
import android.os.UserHandle
|
||||
|
||||
/** Encapsulates a request to show a dialog. */
|
||||
sealed class ShowDialogRequestModel {
|
||||
data class ShowAddUserDialog(
|
||||
val userHandle: UserHandle,
|
||||
val isKeyguardShowing: Boolean,
|
||||
val showEphemeralMessage: Boolean,
|
||||
) : ShowDialogRequestModel()
|
||||
|
||||
data class ShowUserCreationDialog(
|
||||
val isGuest: Boolean,
|
||||
) : ShowDialogRequestModel()
|
||||
|
||||
data class ShowExitGuestDialog(
|
||||
val guestUserId: Int,
|
||||
val targetUserId: Int,
|
||||
val isGuestEphemeral: Boolean,
|
||||
val isKeyguardShowing: Boolean,
|
||||
val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
|
||||
) : ShowDialogRequestModel()
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* 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.app.admin.DevicePolicyManager
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.logging.UiEventLogger
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController
|
||||
import com.android.systemui.user.data.repository.FakeUserRepository
|
||||
import com.android.systemui.user.domain.model.ShowDialogRequestModel
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.kotlinArgumentCaptor
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.TestCoroutineScope
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class GuestUserInteractorTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var manager: UserManager
|
||||
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
|
||||
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
|
||||
@Mock private lateinit var uiEventLogger: UiEventLogger
|
||||
@Mock private lateinit var showDialog: (ShowDialogRequestModel) -> Unit
|
||||
@Mock private lateinit var dismissDialog: () -> Unit
|
||||
@Mock private lateinit var selectUser: (Int) -> Unit
|
||||
@Mock private lateinit var switchUser: (Int) -> Unit
|
||||
|
||||
private lateinit var underTest: GuestUserInteractor
|
||||
|
||||
private lateinit var scope: TestCoroutineScope
|
||||
private lateinit var repository: FakeUserRepository
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(manager.createGuest(any())).thenReturn(GUEST_USER_INFO)
|
||||
|
||||
scope = TestCoroutineScope()
|
||||
repository = FakeUserRepository()
|
||||
repository.setUserInfos(ALL_USERS)
|
||||
|
||||
underTest =
|
||||
GuestUserInteractor(
|
||||
applicationContext = context,
|
||||
applicationScope = scope,
|
||||
mainDispatcher = IMMEDIATE,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
manager = manager,
|
||||
repository = repository,
|
||||
deviceProvisionedController = deviceProvisionedController,
|
||||
devicePolicyManager = devicePolicyManager,
|
||||
refreshUsersScheduler =
|
||||
RefreshUsersScheduler(
|
||||
applicationScope = scope,
|
||||
mainDispatcher = IMMEDIATE,
|
||||
repository = repository,
|
||||
),
|
||||
uiEventLogger = uiEventLogger,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onDeviceBootCompleted - allowed to add - create guest`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
setAllowedToAdd()
|
||||
|
||||
underTest.onDeviceBootCompleted()
|
||||
|
||||
verify(manager).createGuest(any())
|
||||
verify(deviceProvisionedController, never()).addCallback(any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onDeviceBootCompleted - await provisioning - and create guest`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
setAllowedToAdd(isAllowed = false)
|
||||
underTest.onDeviceBootCompleted()
|
||||
val captor =
|
||||
kotlinArgumentCaptor<DeviceProvisionedController.DeviceProvisionedListener>()
|
||||
verify(deviceProvisionedController).addCallback(captor.capture())
|
||||
|
||||
setAllowedToAdd(isAllowed = true)
|
||||
captor.value.onDeviceProvisionedChanged()
|
||||
|
||||
verify(manager).createGuest(any())
|
||||
verify(deviceProvisionedController).removeCallback(captor.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createAndSwitchTo() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
underTest.createAndSwitchTo(
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
selectUser = selectUser,
|
||||
)
|
||||
|
||||
verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
|
||||
verify(manager).createGuest(any())
|
||||
verify(dismissDialog).invoke()
|
||||
verify(selectUser).invoke(GUEST_USER_INFO.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `createAndSwitchTo - fails to create - does not switch to`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(manager.createGuest(any())).thenReturn(null)
|
||||
|
||||
underTest.createAndSwitchTo(
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
selectUser = selectUser,
|
||||
)
|
||||
|
||||
verify(showDialog).invoke(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
|
||||
verify(manager).createGuest(any())
|
||||
verify(dismissDialog).invoke()
|
||||
verify(selectUser, never()).invoke(anyInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit - returns to target user`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
repository.setSelectedUserInfo(GUEST_USER_INFO)
|
||||
|
||||
val targetUserId = NON_GUEST_USER_INFO.id
|
||||
underTest.exit(
|
||||
guestUserId = GUEST_USER_INFO.id,
|
||||
targetUserId = targetUserId,
|
||||
forceRemoveGuestOnExit = false,
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
switchUser = switchUser,
|
||||
)
|
||||
|
||||
verify(manager, never()).markGuestForDeletion(anyInt())
|
||||
verify(manager, never()).removeUser(anyInt())
|
||||
verify(switchUser).invoke(targetUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit - returns to last non-guest`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val expectedUserId = NON_GUEST_USER_INFO.id
|
||||
whenever(manager.getUserInfo(expectedUserId)).thenReturn(NON_GUEST_USER_INFO)
|
||||
repository.lastSelectedNonGuestUserId = expectedUserId
|
||||
repository.setSelectedUserInfo(GUEST_USER_INFO)
|
||||
|
||||
underTest.exit(
|
||||
guestUserId = GUEST_USER_INFO.id,
|
||||
targetUserId = UserHandle.USER_NULL,
|
||||
forceRemoveGuestOnExit = false,
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
switchUser = switchUser,
|
||||
)
|
||||
|
||||
verify(manager, never()).markGuestForDeletion(anyInt())
|
||||
verify(manager, never()).removeUser(anyInt())
|
||||
verify(switchUser).invoke(expectedUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit - last non-guest was removed - returns to system`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val removedUserId = 310
|
||||
repository.lastSelectedNonGuestUserId = removedUserId
|
||||
repository.setSelectedUserInfo(GUEST_USER_INFO)
|
||||
|
||||
underTest.exit(
|
||||
guestUserId = GUEST_USER_INFO.id,
|
||||
targetUserId = UserHandle.USER_NULL,
|
||||
forceRemoveGuestOnExit = false,
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
switchUser = switchUser,
|
||||
)
|
||||
|
||||
verify(manager, never()).markGuestForDeletion(anyInt())
|
||||
verify(manager, never()).removeUser(anyInt())
|
||||
verify(switchUser).invoke(UserHandle.USER_SYSTEM)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit - guest was ephemeral - it is removed`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
|
||||
repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO))
|
||||
repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO)
|
||||
val targetUserId = NON_GUEST_USER_INFO.id
|
||||
|
||||
underTest.exit(
|
||||
guestUserId = GUEST_USER_INFO.id,
|
||||
targetUserId = targetUserId,
|
||||
forceRemoveGuestOnExit = false,
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
switchUser = switchUser,
|
||||
)
|
||||
|
||||
verify(manager).markGuestForDeletion(EPHEMERAL_GUEST_USER_INFO.id)
|
||||
verify(manager).removeUser(EPHEMERAL_GUEST_USER_INFO.id)
|
||||
verify(switchUser).invoke(targetUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit - force remove guest - it is removed`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
|
||||
repository.setSelectedUserInfo(GUEST_USER_INFO)
|
||||
val targetUserId = NON_GUEST_USER_INFO.id
|
||||
|
||||
underTest.exit(
|
||||
guestUserId = GUEST_USER_INFO.id,
|
||||
targetUserId = targetUserId,
|
||||
forceRemoveGuestOnExit = true,
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
switchUser = switchUser,
|
||||
)
|
||||
|
||||
verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
|
||||
verify(manager).removeUser(GUEST_USER_INFO.id)
|
||||
verify(switchUser).invoke(targetUserId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit - selected different from guest user - do nothing`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
|
||||
|
||||
underTest.exit(
|
||||
guestUserId = GUEST_USER_INFO.id,
|
||||
targetUserId = 123,
|
||||
forceRemoveGuestOnExit = false,
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
switchUser = switchUser,
|
||||
)
|
||||
|
||||
verifyDidNotExit()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `exit - selected is actually not a guest user - do nothing`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
repository.setSelectedUserInfo(NON_GUEST_USER_INFO)
|
||||
|
||||
underTest.exit(
|
||||
guestUserId = NON_GUEST_USER_INFO.id,
|
||||
targetUserId = 123,
|
||||
forceRemoveGuestOnExit = false,
|
||||
showDialog = showDialog,
|
||||
dismissDialog = dismissDialog,
|
||||
switchUser = switchUser,
|
||||
)
|
||||
|
||||
verifyDidNotExit()
|
||||
}
|
||||
|
||||
private fun setAllowedToAdd(isAllowed: Boolean = true) {
|
||||
whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(isAllowed)
|
||||
whenever(devicePolicyManager.isDeviceManaged).thenReturn(!isAllowed)
|
||||
}
|
||||
|
||||
private fun verifyDidNotExit() {
|
||||
verify(manager, never()).getUserInfo(anyInt())
|
||||
verify(manager, never()).markGuestForDeletion(anyInt())
|
||||
verify(uiEventLogger, never()).log(any())
|
||||
verify(showDialog, never()).invoke(any())
|
||||
verify(dismissDialog, never()).invoke()
|
||||
verify(switchUser, never()).invoke(anyInt())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
private val NON_GUEST_USER_INFO =
|
||||
UserInfo(
|
||||
/* id= */ 818,
|
||||
/* name= */ "non_guest",
|
||||
/* flags= */ 0,
|
||||
)
|
||||
private val GUEST_USER_INFO =
|
||||
UserInfo(
|
||||
/* id= */ 669,
|
||||
/* name= */ "guest",
|
||||
/* iconPath= */ "",
|
||||
/* flags= */ 0,
|
||||
UserManager.USER_TYPE_FULL_GUEST,
|
||||
)
|
||||
private val EPHEMERAL_GUEST_USER_INFO =
|
||||
UserInfo(
|
||||
/* id= */ 669,
|
||||
/* name= */ "guest",
|
||||
/* iconPath= */ "",
|
||||
/* flags= */ UserInfo.FLAG_EPHEMERAL,
|
||||
UserManager.USER_TYPE_FULL_GUEST,
|
||||
)
|
||||
private val ALL_USERS =
|
||||
listOf(
|
||||
NON_GUEST_USER_INFO,
|
||||
GUEST_USER_INFO,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user