1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.intentresolver.data.repository
18
19 import android.content.pm.UserInfo
20 import android.os.UserHandle
21 import android.os.UserHandle.SYSTEM
22 import android.os.UserHandle.USER_SYSTEM
23 import android.os.UserManager
24 import com.android.intentresolver.coroutines.collectLastValue
25 import com.android.intentresolver.platform.FakeUserManager
26 import com.android.intentresolver.platform.FakeUserManager.ProfileType
27 import com.android.intentresolver.shared.model.User
28 import com.android.intentresolver.shared.model.User.Role
29 import com.google.common.truth.Truth.assertThat
30 import com.google.common.truth.Truth.assertWithMessage
31 import kotlinx.coroutines.Dispatchers
32 import kotlinx.coroutines.flow.flowOf
33 import kotlinx.coroutines.test.TestScope
34 import kotlinx.coroutines.test.runTest
35 import org.junit.Test
36 import org.mockito.kotlin.any
37 import org.mockito.kotlin.doReturn
38 import org.mockito.kotlin.mock
39
40 internal class UserRepositoryImplTest {
41 private val userManager = FakeUserManager()
42 private val userState = userManager.state
43
44 @Test
<lambda>null45 fun initialization() = runTest {
46 val repo = createUserRepository(userManager)
47 val users by collectLastValue(repo.users)
48
49 assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull()
50 assertThat(users)
51 .containsExactly(User(userState.primaryUserHandle.identifier, Role.PERSONAL))
52 }
53
54 @Test
<lambda>null55 fun createProfile() = runTest {
56 val repo = createUserRepository(userManager)
57 val users by collectLastValue(repo.users)
58
59 assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull()
60 assertThat(users).hasSize(1)
61
62 val profile = userState.createProfile(ProfileType.WORK)
63 assertThat(users).hasSize(2)
64 assertThat(users).contains(User(profile.identifier, Role.WORK))
65 }
66
67 @Test
<lambda>null68 fun removeProfile() = runTest {
69 val repo = createUserRepository(userManager)
70 val users by collectLastValue(repo.users)
71
72 assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull()
73 val work = userState.createProfile(ProfileType.WORK)
74 assertThat(users).contains(User(work.identifier, Role.WORK))
75
76 userState.removeProfile(work)
77 assertThat(users).doesNotContain(User(work.identifier, Role.WORK))
78 }
79
80 @Test
<lambda>null81 fun isAvailable() = runTest {
82 val repo = createUserRepository(userManager)
83 val work = userState.createProfile(ProfileType.WORK)
84 val workUser = User(work.identifier, Role.WORK)
85
86 val available by collectLastValue(repo.availability)
87 assertThat(available?.get(workUser)).isTrue()
88
89 userState.setQuietMode(work, true)
90 assertThat(available?.get(workUser)).isFalse()
91
92 userState.setQuietMode(work, false)
93 assertThat(available?.get(workUser)).isTrue()
94 }
95
96 @Test
<lambda>null97 fun onHandleAvailabilityChange_userStateMaintained() = runTest {
98 val repo = createUserRepository(userManager)
99 val private = userState.createProfile(ProfileType.PRIVATE)
100 val privateUser = User(private.identifier, Role.PRIVATE)
101
102 val users by collectLastValue(repo.users)
103
104 repo.requestState(privateUser, false)
105 repo.requestState(privateUser, true)
106
107 assertWithMessage("users.size").that(users?.size ?: 0).isEqualTo(2) // personal + private
108
109 assertWithMessage("No duplicate IDs")
110 .that(users?.count { it.id == private.identifier })
111 .isEqualTo(1)
112 }
113
114 @Test
<lambda>null115 fun requestState() = runTest {
116 val repo = createUserRepository(userManager)
117 val work = userState.createProfile(ProfileType.WORK)
118 val workUser = User(work.identifier, Role.WORK)
119
120 val available by collectLastValue(repo.availability)
121 assertThat(available?.get(workUser)).isTrue()
122
123 repo.requestState(workUser, false)
124 assertThat(available?.get(workUser)).isFalse()
125
126 repo.requestState(workUser, true)
127 assertThat(available?.get(workUser)).isTrue()
128 }
129
130 /**
131 * This and all the 'recovers_from_*' tests below all configure a static event flow instead of
132 * using [FakeUserManager]. These tests verify that a invalid broadcast causes the flow to
133 * reinitialize with the user profile group.
134 */
135 @Test
<lambda>null136 fun recovers_from_invalid_profile_added_event() = runTest {
137 val userManager =
138 mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
139 val events = flowOf(ProfileAdded(UserHandle.of(UserHandle.USER_NULL)))
140 val repo =
141 UserRepositoryImpl(
142 profileParent = SYSTEM,
143 userManager = userManager,
144 userEvents = events,
145 scope = backgroundScope,
146 backgroundDispatcher = Dispatchers.Unconfined
147 )
148 val users by collectLastValue(repo.users)
149
150 assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull()
151 assertThat(users).containsExactly(User(USER_SYSTEM, Role.PERSONAL))
152 }
153
154 @Test
<lambda>null155 fun recovers_from_invalid_profile_removed_event() = runTest {
156 val userManager =
157 mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
158 val events = flowOf(ProfileRemoved(UserHandle.of(UserHandle.USER_NULL)))
159 val repo =
160 UserRepositoryImpl(
161 profileParent = SYSTEM,
162 userManager = userManager,
163 userEvents = events,
164 scope = backgroundScope,
165 backgroundDispatcher = Dispatchers.Unconfined
166 )
167 val users by collectLastValue(repo.users)
168
169 assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull()
170 assertThat(users).containsExactly(User(USER_SYSTEM, Role.PERSONAL))
171 }
172
173 @Test
<lambda>null174 fun recovers_from_invalid_profile_available_event() = runTest {
175 val userManager =
176 mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
177 val events = flowOf(AvailabilityChange(UserHandle.of(UserHandle.USER_NULL)))
178 val repo =
179 UserRepositoryImpl(SYSTEM, userManager, events, backgroundScope, Dispatchers.Unconfined)
180 val users by collectLastValue(repo.users)
181
182 assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull()
183 assertThat(users).containsExactly(User(USER_SYSTEM, Role.PERSONAL))
184 }
185
186 @Test
<lambda>null187 fun recovers_from_unknown_event() = runTest {
188 val userManager =
189 mockUserManager(validUser = USER_SYSTEM, invalidUser = UserHandle.USER_NULL)
190 val events = flowOf(UnknownEvent("UNKNOWN_EVENT"))
191 val repo =
192 UserRepositoryImpl(
193 profileParent = SYSTEM,
194 userManager = userManager,
195 userEvents = events,
196 scope = backgroundScope,
197 backgroundDispatcher = Dispatchers.Unconfined
198 )
199 val users by collectLastValue(repo.users)
200
201 assertWithMessage("collectLastValue(repo.users)").that(users).isNotNull()
202 assertThat(users).containsExactly(User(USER_SYSTEM, Role.PERSONAL))
203 }
204 }
205
206 @Suppress("SameParameterValue")
mockUserManagernull207 private fun mockUserManager(validUser: Int, invalidUser: Int) =
208 mock<UserManager> {
209 val info = UserInfo(validUser, "", "", UserInfo.FLAG_FULL)
210 on { getEnabledProfiles(any()) } doReturn listOf(info)
211 on { getUserInfo(validUser) } doReturn info
212 on { getEnabledProfiles(invalidUser) } doReturn listOf()
213 on { getUserInfo(invalidUser) } doReturn null
214 }
215
createUserRepositorynull216 private fun TestScope.createUserRepository(userManager: FakeUserManager): UserRepositoryImpl {
217 return UserRepositoryImpl(
218 profileParent = userManager.state.primaryUserHandle,
219 userManager = userManager,
220 userEvents = userManager.state.userEvents,
221 scope = backgroundScope,
222 backgroundDispatcher = Dispatchers.Unconfined
223 )
224 }
225