summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt166
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserRepository.kt82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt217
8 files changed, 567 insertions, 23 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index 469d54ff8ffa..5b522dcc4885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -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";
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
new file mode 100644
index 000000000000..305b5ee920a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -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"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
new file mode 100644
index 000000000000..18ae1070e1bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepositoryModule.kt
@@ -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
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
index 67999f381a0e..cf6da9a60d78 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -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)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt
new file mode 100644
index 000000000000..823bf74dc0f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt
@@ -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,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
new file mode 100644
index 000000000000..bf7977a600e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserModel.kt
@@ -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,
+)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
new file mode 100644
index 000000000000..20f1e367944f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -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
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
new file mode 100644
index 000000000000..6b466e1ac2d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -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
+ }
+}