User data layer.
UserSwitcherActivity refactor: CL 4/7 This is the data layer for the UserSwitcherActivity refactor. It includes a repository and a couple of shared models that the repository exposes. Bug: 243844359 Test: Unit tests included. Also tested as part of the big manual test in CL 7/7 in this chain Change-Id: I4cb5b1c4f018def88cda5864de51c644c6d8ce0f
This commit is contained in:
@@ -19,6 +19,7 @@ package com.android.systemui.user;
|
||||
import android.app.Activity;
|
||||
|
||||
import com.android.settingslib.users.EditUserInfoController;
|
||||
import com.android.systemui.user.data.repository.UserRepositoryModule;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
@@ -29,7 +30,11 @@ import dagger.multibindings.IntoMap;
|
||||
/**
|
||||
* Dagger module for User related classes.
|
||||
*/
|
||||
@Module
|
||||
@Module(
|
||||
includes = {
|
||||
UserRepositoryModule.class,
|
||||
}
|
||||
)
|
||||
public abstract class UserModule {
|
||||
|
||||
private static final String FILE_PROVIDER_AUTHORITY = "com.android.systemui.fileprovider";
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.user.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.UserManager
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import com.android.internal.util.UserIcons
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.common.shared.model.Text
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController
|
||||
import com.android.systemui.user.data.source.UserRecord
|
||||
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
|
||||
import com.android.systemui.user.shared.model.UserActionModel
|
||||
import com.android.systemui.user.shared.model.UserModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Acts as source of truth for user related data.
|
||||
*
|
||||
* Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
|
||||
* upstream changes.
|
||||
*/
|
||||
interface UserRepository {
|
||||
/** List of all users on the device. */
|
||||
val users: Flow<List<UserModel>>
|
||||
|
||||
/** The currently-selected user. */
|
||||
val selectedUser: Flow<UserModel>
|
||||
|
||||
/** List of available user-related actions. */
|
||||
val actions: Flow<List<UserActionModel>>
|
||||
|
||||
/** Whether actions are available even when locked. */
|
||||
val isActionableWhenLocked: Flow<Boolean>
|
||||
|
||||
/** Whether the device is configured to always have a guest user available. */
|
||||
val isGuestUserAutoCreated: Boolean
|
||||
|
||||
/** Whether the guest user is currently being reset. */
|
||||
val isGuestUserResetting: Boolean
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class UserRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val appContext: Context,
|
||||
private val manager: UserManager,
|
||||
controller: UserSwitcherController,
|
||||
) : UserRepository {
|
||||
|
||||
private val userRecords: Flow<List<UserRecord>> = conflatedCallbackFlow {
|
||||
fun send() {
|
||||
trySendWithFailureLogging(
|
||||
controller.users,
|
||||
TAG,
|
||||
)
|
||||
}
|
||||
|
||||
val callback = UserSwitcherController.UserSwitchCallback { send() }
|
||||
|
||||
controller.addUserSwitchCallback(callback)
|
||||
send()
|
||||
|
||||
awaitClose { controller.removeUserSwitchCallback(callback) }
|
||||
}
|
||||
|
||||
override val users: Flow<List<UserModel>> =
|
||||
userRecords.map { records -> records.filter { it.isUser() }.map { it.toUserModel() } }
|
||||
|
||||
override val selectedUser: Flow<UserModel> =
|
||||
users.map { users -> users.first { user -> user.isSelected } }
|
||||
|
||||
override val actions: Flow<List<UserActionModel>> =
|
||||
userRecords.map { records -> records.filter { it.isNotUser() }.map { it.toActionModel() } }
|
||||
|
||||
override val isActionableWhenLocked: Flow<Boolean> = controller.addUsersFromLockScreen
|
||||
|
||||
override val isGuestUserAutoCreated: Boolean = controller.isGuestUserAutoCreated
|
||||
|
||||
override val isGuestUserResetting: Boolean = controller.isGuestUserResetting
|
||||
|
||||
private fun UserRecord.isUser(): Boolean {
|
||||
return when {
|
||||
isAddUser -> false
|
||||
isAddSupervisedUser -> false
|
||||
isGuest -> info != null
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private fun UserRecord.isNotUser(): Boolean {
|
||||
return !isUser()
|
||||
}
|
||||
|
||||
private fun UserRecord.toUserModel(): UserModel {
|
||||
return UserModel(
|
||||
id = resolveId(),
|
||||
name = getUserName(this),
|
||||
image = getUserImage(this),
|
||||
isSelected = isCurrent,
|
||||
isSelectable = isSwitchToEnabled || isGuest,
|
||||
)
|
||||
}
|
||||
|
||||
private fun UserRecord.toActionModel(): UserActionModel {
|
||||
return when {
|
||||
isAddUser -> UserActionModel.ADD_USER
|
||||
isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
|
||||
isGuest -> UserActionModel.ENTER_GUEST_MODE
|
||||
else -> error("Don't know how to convert to UserActionModel: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserName(record: UserRecord): Text {
|
||||
val resourceId: Int? = LegacyUserUiHelper.getGuestUserRecordNameResourceId(record)
|
||||
return if (resourceId != null) {
|
||||
Text.Resource(resourceId)
|
||||
} else {
|
||||
Text.Loaded(checkNotNull(record.info).name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserImage(record: UserRecord): Drawable {
|
||||
if (record.isGuest) {
|
||||
return checkNotNull(
|
||||
AppCompatResources.getDrawable(appContext, R.drawable.ic_account_circle)
|
||||
)
|
||||
}
|
||||
|
||||
val userId = checkNotNull(record.info?.id)
|
||||
return manager.getUserIcon(userId)?.let { userSelectedIcon ->
|
||||
BitmapDrawable(userSelectedIcon)
|
||||
}
|
||||
?: UserIcons.getDefaultUserIcon(appContext.resources, userId, /* light= */ false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "UserRepository"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.user.data.repository
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
|
||||
@Module
|
||||
interface UserRepositoryModule {
|
||||
@Binds fun bindRepository(impl: UserRepositoryImpl): UserRepository
|
||||
}
|
||||
@@ -20,41 +20,29 @@ import android.content.pm.UserInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.os.UserHandle
|
||||
|
||||
/**
|
||||
* Encapsulates raw data for a user or an option item related to managing users on the device.
|
||||
*/
|
||||
/** Encapsulates raw data for a user or an option item related to managing users on the device. */
|
||||
data class UserRecord(
|
||||
/** Relevant user information. If `null`, this record is not a user but an option item. */
|
||||
@JvmField
|
||||
val info: UserInfo? = null,
|
||||
@JvmField val info: UserInfo? = null,
|
||||
/** An image representing the user. */
|
||||
@JvmField
|
||||
val picture: Bitmap? = null,
|
||||
@JvmField val picture: Bitmap? = null,
|
||||
/** Whether this record represents an option to switch to a guest user. */
|
||||
@JvmField
|
||||
val isGuest: Boolean = false,
|
||||
@JvmField val isGuest: Boolean = false,
|
||||
/** Whether this record represents the currently-selected user. */
|
||||
@JvmField
|
||||
val isCurrent: Boolean = false,
|
||||
@JvmField val isCurrent: Boolean = false,
|
||||
/** Whether this record represents an option to add another user to the device. */
|
||||
@JvmField
|
||||
val isAddUser: Boolean = false,
|
||||
@JvmField val isAddUser: Boolean = false,
|
||||
/**
|
||||
* If true, the record is only available if unlocked or if the user has granted permission to
|
||||
* access this user action whilst on the device is locked.
|
||||
*/
|
||||
@JvmField
|
||||
val isRestricted: Boolean = false,
|
||||
@JvmField val isRestricted: Boolean = false,
|
||||
/** Whether it is possible to switch to this user. */
|
||||
@JvmField
|
||||
val isSwitchToEnabled: Boolean = false,
|
||||
@JvmField val isSwitchToEnabled: Boolean = false,
|
||||
/** Whether this record represents an option to add another supervised user to the device. */
|
||||
@JvmField
|
||||
val isAddSupervisedUser: Boolean = false,
|
||||
@JvmField val isAddSupervisedUser: Boolean = false,
|
||||
) {
|
||||
/**
|
||||
* Returns a new instance of [UserRecord] with its [isCurrent] set to the given value.
|
||||
*/
|
||||
/** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
|
||||
fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
|
||||
return copy(isCurrent = isCurrent)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.shared.model
|
||||
|
||||
enum class UserActionModel {
|
||||
ENTER_GUEST_MODE,
|
||||
ADD_USER,
|
||||
ADD_SUPERVISED_USER,
|
||||
NAVIGATE_TO_USER_MANAGEMENT,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.shared.model
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.android.systemui.common.shared.model.Text
|
||||
|
||||
/** Represents a single user on the device. */
|
||||
data class UserModel(
|
||||
/** ID of the user, unique across all users on this device. */
|
||||
val id: Int,
|
||||
/** Human-facing name for this user. */
|
||||
val name: Text,
|
||||
/** Human-facing image for this user. */
|
||||
val image: Drawable,
|
||||
/** Whether this user is the currently-selected user. */
|
||||
val isSelected: Boolean,
|
||||
/** Whether this use is selectable. A non-selectable user cannot be switched to. */
|
||||
val isSelectable: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.user.data.repository
|
||||
|
||||
import com.android.systemui.user.shared.model.UserActionModel
|
||||
import com.android.systemui.user.shared.model.UserModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class FakeUserRepository : UserRepository {
|
||||
|
||||
private val _users = MutableStateFlow<List<UserModel>>(emptyList())
|
||||
override val users: Flow<List<UserModel>> = _users.asStateFlow()
|
||||
override val selectedUser: Flow<UserModel> =
|
||||
users.map { models -> models.first { model -> model.isSelected } }
|
||||
|
||||
private val _actions = MutableStateFlow<List<UserActionModel>>(emptyList())
|
||||
override val actions: Flow<List<UserActionModel>> = _actions.asStateFlow()
|
||||
|
||||
private val _isActionableWhenLocked = MutableStateFlow(false)
|
||||
override val isActionableWhenLocked: Flow<Boolean> = _isActionableWhenLocked.asStateFlow()
|
||||
|
||||
private var _isGuestUserAutoCreated: Boolean = false
|
||||
override val isGuestUserAutoCreated: Boolean
|
||||
get() = _isGuestUserAutoCreated
|
||||
private var _isGuestUserResetting: Boolean = false
|
||||
override val isGuestUserResetting: Boolean
|
||||
get() = _isGuestUserResetting
|
||||
|
||||
fun setUsers(models: List<UserModel>) {
|
||||
_users.value = models
|
||||
}
|
||||
|
||||
fun setSelectedUser(userId: Int) {
|
||||
check(_users.value.find { it.id == userId } != null) {
|
||||
"Cannot select a user with ID $userId - no user with that ID found!"
|
||||
}
|
||||
|
||||
setUsers(
|
||||
_users.value.map { model ->
|
||||
when {
|
||||
model.isSelected && model.id != userId -> model.copy(isSelected = false)
|
||||
!model.isSelected && model.id == userId -> model.copy(isSelected = true)
|
||||
else -> model
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setActions(models: List<UserActionModel>) {
|
||||
_actions.value = models
|
||||
}
|
||||
|
||||
fun setActionableWhenLocked(value: Boolean) {
|
||||
_isActionableWhenLocked.value = value
|
||||
}
|
||||
|
||||
fun setGuestUserAutoCreated(value: Boolean) {
|
||||
_isGuestUserAutoCreated = value
|
||||
}
|
||||
|
||||
fun setGuestUserResetting(value: Boolean) {
|
||||
_isGuestUserResetting = value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.systemui.user.data.repository
|
||||
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.UserManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController
|
||||
import com.android.systemui.user.data.source.UserRecord
|
||||
import com.android.systemui.user.shared.model.UserActionModel
|
||||
import com.android.systemui.user.shared.model.UserModel
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class UserRepositoryImplTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var manager: UserManager
|
||||
@Mock private lateinit var controller: UserSwitcherController
|
||||
@Captor
|
||||
private lateinit var userSwitchCallbackCaptor:
|
||||
ArgumentCaptor<UserSwitcherController.UserSwitchCallback>
|
||||
|
||||
private lateinit var underTest: UserRepositoryImpl
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(controller.addUsersFromLockScreen).thenReturn(MutableStateFlow(false))
|
||||
whenever(controller.isGuestUserAutoCreated).thenReturn(false)
|
||||
whenever(controller.isGuestUserResetting).thenReturn(false)
|
||||
|
||||
underTest =
|
||||
UserRepositoryImpl(
|
||||
appContext = context,
|
||||
manager = manager,
|
||||
controller = controller,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `users - registers for updates`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val job = underTest.users.onEach {}.launchIn(this)
|
||||
|
||||
verify(controller).addUserSwitchCallback(any())
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `users - unregisters from updates`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val job = underTest.users.onEach {}.launchIn(this)
|
||||
verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
|
||||
|
||||
job.cancel()
|
||||
|
||||
verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `users - does not include actions`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(controller.users)
|
||||
.thenReturn(
|
||||
arrayListOf(
|
||||
createUserRecord(0, isSelected = true),
|
||||
createActionRecord(UserActionModel.ADD_USER),
|
||||
createUserRecord(1),
|
||||
createUserRecord(2),
|
||||
createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
|
||||
createActionRecord(UserActionModel.ENTER_GUEST_MODE),
|
||||
)
|
||||
)
|
||||
var models: List<UserModel>? = null
|
||||
val job = underTest.users.onEach { models = it }.launchIn(this)
|
||||
|
||||
assertThat(models).hasSize(3)
|
||||
assertThat(models?.get(0)?.id).isEqualTo(0)
|
||||
assertThat(models?.get(0)?.isSelected).isTrue()
|
||||
assertThat(models?.get(1)?.id).isEqualTo(1)
|
||||
assertThat(models?.get(1)?.isSelected).isFalse()
|
||||
assertThat(models?.get(2)?.id).isEqualTo(2)
|
||||
assertThat(models?.get(2)?.isSelected).isFalse()
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectedUser() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(controller.users)
|
||||
.thenReturn(
|
||||
arrayListOf(
|
||||
createUserRecord(0, isSelected = true),
|
||||
createUserRecord(1),
|
||||
createUserRecord(2),
|
||||
)
|
||||
)
|
||||
var id: Int? = null
|
||||
val job = underTest.selectedUser.map { it.id }.onEach { id = it }.launchIn(this)
|
||||
|
||||
assertThat(id).isEqualTo(0)
|
||||
|
||||
whenever(controller.users)
|
||||
.thenReturn(
|
||||
arrayListOf(
|
||||
createUserRecord(0),
|
||||
createUserRecord(1),
|
||||
createUserRecord(2, isSelected = true),
|
||||
)
|
||||
)
|
||||
verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
|
||||
userSwitchCallbackCaptor.value.onUserSwitched()
|
||||
assertThat(id).isEqualTo(2)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `actions - unregisters from updates`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val job = underTest.actions.onEach {}.launchIn(this)
|
||||
verify(controller).addUserSwitchCallback(capture(userSwitchCallbackCaptor))
|
||||
|
||||
job.cancel()
|
||||
|
||||
verify(controller).removeUserSwitchCallback(userSwitchCallbackCaptor.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `actions - registers for updates`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val job = underTest.actions.onEach {}.launchIn(this)
|
||||
|
||||
verify(controller).addUserSwitchCallback(any())
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `actopms - does not include users`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
whenever(controller.users)
|
||||
.thenReturn(
|
||||
arrayListOf(
|
||||
createUserRecord(0, isSelected = true),
|
||||
createActionRecord(UserActionModel.ADD_USER),
|
||||
createUserRecord(1),
|
||||
createUserRecord(2),
|
||||
createActionRecord(UserActionModel.ADD_SUPERVISED_USER),
|
||||
createActionRecord(UserActionModel.ENTER_GUEST_MODE),
|
||||
)
|
||||
)
|
||||
var models: List<UserActionModel>? = null
|
||||
val job = underTest.actions.onEach { models = it }.launchIn(this)
|
||||
|
||||
assertThat(models).hasSize(3)
|
||||
assertThat(models?.get(0)).isEqualTo(UserActionModel.ADD_USER)
|
||||
assertThat(models?.get(1)).isEqualTo(UserActionModel.ADD_SUPERVISED_USER)
|
||||
assertThat(models?.get(2)).isEqualTo(UserActionModel.ENTER_GUEST_MODE)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
private fun createUserRecord(id: Int, isSelected: Boolean = false): UserRecord {
|
||||
return UserRecord(
|
||||
info = UserInfo(id, "name$id", 0),
|
||||
isCurrent = isSelected,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createActionRecord(action: UserActionModel): UserRecord {
|
||||
return UserRecord(
|
||||
isAddUser = action == UserActionModel.ADD_USER,
|
||||
isAddSupervisedUser = action == UserActionModel.ADD_SUPERVISED_USER,
|
||||
isGuest = action == UserActionModel.ENTER_GUEST_MODE,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user