1 /*
2  * Copyright (C) 2023 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.systemui.qs.tiles.impl.custom.data.repository
18 
19 import android.content.ComponentName
20 import android.graphics.drawable.Icon
21 import android.os.UserHandle
22 import android.service.quicksettings.Tile
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.coroutines.collectValues
27 import com.android.systemui.kosmos.Kosmos
28 import com.android.systemui.kosmos.testScope
29 import com.android.systemui.qs.external.TileServiceKey
30 import com.android.systemui.qs.pipeline.shared.TileSpec
31 import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
32 import com.android.systemui.qs.tiles.impl.custom.commons.copy
33 import com.android.systemui.qs.tiles.impl.custom.customTileSpec
34 import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
35 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
36 import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
37 import com.google.common.truth.Truth.assertThat
38 import kotlinx.coroutines.ExperimentalCoroutinesApi
39 import kotlinx.coroutines.flow.first
40 import kotlinx.coroutines.test.runCurrent
41 import kotlinx.coroutines.test.runTest
42 import org.junit.Test
43 import org.junit.runner.RunWith
44 
45 @SmallTest
46 @RunWith(AndroidJUnit4::class)
47 @OptIn(ExperimentalCoroutinesApi::class)
48 class CustomTileRepositoryTest : SysuiTestCase() {
49 
<lambda>null50     private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) }
51     private val underTest: CustomTileRepository =
<lambda>null52         with(kosmos) {
53             CustomTileRepositoryImpl(
54                 customTileSpec,
55                 customTileStatePersister,
56                 packageManagerAdapterFacade.packageManagerAdapter,
57                 testScope.testScheduler,
58             )
59         }
60 
61     @Test
persistableTileIsRestoredForUsernull62     fun persistableTileIsRestoredForUser() =
63         with(kosmos) {
64             testScope.runTest {
65                 customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
66                 customTileStatePersister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
67 
68                 underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
69                 runCurrent()
70 
71                 assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
72                 assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
73             }
74         }
75 
76     @Test
notPersistableTileIsNotRestorednull77     fun notPersistableTileIsNotRestored() =
78         with(kosmos) {
79             testScope.runTest {
80                 customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
81                 val tiles = collectValues(underTest.getTiles(TEST_USER_1))
82 
83                 underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
84                 runCurrent()
85 
86                 assertThat(tiles()).isEmpty()
87             }
88         }
89 
90     @Test
emptyPersistedStateIsHandlednull91     fun emptyPersistedStateIsHandled() =
92         with(kosmos) {
93             testScope.runTest {
94                 val tiles = collectValues(underTest.getTiles(TEST_USER_1))
95 
96                 underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
97                 runCurrent()
98 
99                 assertThat(tiles()).isEmpty()
100             }
101         }
102 
103     @Test
updatingWithPersistableTilePersistsnull104     fun updatingWithPersistableTilePersists() =
105         with(kosmos) {
106             testScope.runTest {
107                 underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
108                 runCurrent()
109 
110                 assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
111                     .isEqualTo(TEST_TILE_1)
112             }
113         }
114 
115     @Test
updatingWithNotPersistableTileDoesntPersistnull116     fun updatingWithNotPersistableTileDoesntPersist() =
117         with(kosmos) {
118             testScope.runTest {
119                 underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
120                 runCurrent()
121 
122                 assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
123             }
124         }
125 
126     @Test
updateWithTileEmitsnull127     fun updateWithTileEmits() =
128         with(kosmos) {
129             testScope.runTest {
130                 underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
131                 runCurrent()
132 
133                 assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
134                 assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
135             }
136         }
137 
138     @Test
updatingPeristableWithDefaultsPersistsnull139     fun updatingPeristableWithDefaultsPersists() =
140         with(kosmos) {
141             testScope.runTest {
142                 underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
143                 runCurrent()
144 
145                 assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
146                     .isEqualTo(TEST_TILE_1)
147             }
148         }
149 
150     @Test
updatingNotPersistableWithDefaultsDoesntPersistnull151     fun updatingNotPersistableWithDefaultsDoesntPersist() =
152         with(kosmos) {
153             testScope.runTest {
154                 underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
155                 runCurrent()
156 
157                 assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
158             }
159         }
160 
161     @Test
updatingPeristableWithErrorDefaultsDoesntPersistnull162     fun updatingPeristableWithErrorDefaultsDoesntPersist() =
163         with(kosmos) {
164             testScope.runTest {
165                 underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
166                 runCurrent()
167 
168                 assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
169             }
170         }
171 
172     @Test
updateWithDefaultsEmitsnull173     fun updateWithDefaultsEmits() =
174         with(kosmos) {
175             testScope.runTest {
176                 underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
177                 runCurrent()
178 
179                 assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
180                 assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
181             }
182         }
183 
184     @Test
getTileForAnotherUserReturnsNullnull185     fun getTileForAnotherUserReturnsNull() =
186         with(kosmos) {
187             testScope.runTest {
188                 underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
189                 runCurrent()
190 
191                 assertThat(underTest.getTile(TEST_USER_2)).isNull()
192             }
193         }
194 
195     @Test
getTilesForAnotherUserEmptynull196     fun getTilesForAnotherUserEmpty() =
197         with(kosmos) {
198             testScope.runTest {
199                 val tiles = collectValues(underTest.getTiles(TEST_USER_2))
200 
201                 underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
202                 runCurrent()
203 
204                 assertThat(tiles()).isEmpty()
205             }
206         }
207 
208     @Test
updatingWithTileForTheSameUserAddsDatanull209     fun updatingWithTileForTheSameUserAddsData() =
210         with(kosmos) {
211             testScope.runTest {
212                 underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
213                 runCurrent()
214 
215                 underTest.updateWithTile(
216                     TEST_USER_1,
217                     Tile().apply { subtitle = "test_subtitle" },
218                     true
219                 )
220                 runCurrent()
221 
222                 val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
223                 assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
224                 assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
225             }
226         }
227 
228     @Test
updatingWithTileForAnotherUserOverridesTilenull229     fun updatingWithTileForAnotherUserOverridesTile() =
230         with(kosmos) {
231             testScope.runTest {
232                 underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
233                 runCurrent()
234 
235                 val tiles = collectValues(underTest.getTiles(TEST_USER_2))
236                 underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
237                 runCurrent()
238 
239                 assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
240                 assertThat(tiles()).hasSize(1)
241                 assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
242             }
243         }
244 
245     @Test
updatingWithDefaultsForTheSameUserAddsDatanull246     fun updatingWithDefaultsForTheSameUserAddsData() =
247         with(kosmos) {
248             testScope.runTest {
249                 underTest.updateWithTile(
250                     TEST_USER_1,
251                     Tile().apply { subtitle = "test_subtitle" },
252                     true
253                 )
254                 runCurrent()
255 
256                 underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
257                 runCurrent()
258 
259                 val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
260                 assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
261                 assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
262             }
263         }
264 
265     @Test
updatingWithDefaultsForAnotherUserOverridesTilenull266     fun updatingWithDefaultsForAnotherUserOverridesTile() =
267         with(kosmos) {
268             testScope.runTest {
269                 underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
270                 runCurrent()
271 
272                 val tiles = collectValues(underTest.getTiles(TEST_USER_2))
273                 underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
274                 runCurrent()
275 
276                 assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
277                 assertThat(tiles()).hasSize(1)
278                 assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
279             }
280         }
281 
282     @Test
isActiveFollowsPackageManagerAdapter_user0null283     fun isActiveFollowsPackageManagerAdapter_user0() =
284         with(kosmos) {
285             testScope.runTest {
286                 packageManagerAdapterFacade.setExclusiveForUser(0)
287 
288                 underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
289                 packageManagerAdapterFacade.setIsActive(false)
290                 assertThat(underTest.isTileActive()).isFalse()
291 
292                 packageManagerAdapterFacade.setIsActive(true)
293                 assertThat(underTest.isTileActive()).isTrue()
294             }
295         }
296 
297     @Test
isToggleableFollowsPackageManagerAdapternull298     fun isToggleableFollowsPackageManagerAdapter() =
299         with(kosmos) {
300             testScope.runTest {
301                 underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
302                 packageManagerAdapterFacade.setIsToggleable(false)
303                 assertThat(underTest.isTileToggleable()).isFalse()
304 
305                 packageManagerAdapterFacade.setIsToggleable(true)
306                 assertThat(underTest.isTileToggleable()).isTrue()
307             }
308         }
309 
310     @Test
isActiveFollowsPackageManagerAdapter_user10_withAdapterForUser10null311     fun isActiveFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
312         with(kosmos) {
313             testScope.runTest {
314                 packageManagerAdapterFacade.setExclusiveForUser(10)
315 
316                 underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
317                 packageManagerAdapterFacade.setIsActive(false)
318                 assertThat(underTest.isTileActive()).isFalse()
319 
320                 packageManagerAdapterFacade.setIsActive(true)
321                 assertThat(underTest.isTileActive()).isTrue()
322             }
323         }
324 
325     @Test
isToggleableFollowsPackageManagerAdapter_user10_withAdapterForUser10null326     fun isToggleableFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
327         with(kosmos) {
328             testScope.runTest {
329                 packageManagerAdapterFacade.setExclusiveForUser(10)
330 
331                 underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
332                 packageManagerAdapterFacade.setIsToggleable(false)
333                 assertThat(underTest.isTileToggleable()).isFalse()
334 
335                 packageManagerAdapterFacade.setIsToggleable(true)
336                 assertThat(underTest.isTileToggleable()).isTrue()
337             }
338         }
339 
340     @Test
isActiveDoesntFollowPackageManagerAdapter_user10null341     fun isActiveDoesntFollowPackageManagerAdapter_user10() =
342         with(kosmos) {
343             testScope.runTest {
344                 packageManagerAdapterFacade.setExclusiveForUser(0)
345 
346                 underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
347                 packageManagerAdapterFacade.setIsActive(false)
348                 assertThat(underTest.isTileActive()).isFalse()
349 
350                 packageManagerAdapterFacade.setIsActive(true)
351                 assertThat(underTest.isTileActive()).isFalse()
352             }
353         }
354 
355     @Test
isToggleableDoesntFollowPackageManagerAdapter_user10null356     fun isToggleableDoesntFollowPackageManagerAdapter_user10() =
357         with(kosmos) {
358             testScope.runTest {
359                 packageManagerAdapterFacade.setExclusiveForUser(0)
360 
361                 underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
362                 packageManagerAdapterFacade.setIsToggleable(false)
363                 assertThat(underTest.isTileToggleable()).isFalse()
364 
365                 packageManagerAdapterFacade.setIsToggleable(true)
366                 assertThat(underTest.isTileToggleable()).isFalse()
367             }
368         }
369 
370     private companion object {
371 
372         val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
373 
374         val TEST_USER_1 = UserHandle.of(1)!!
<lambda>null375         val TEST_TILE_1 by lazy {
376             Tile().apply {
377                 label = "test_tile_1"
378                 icon = Icon.createWithContentUri("file://test_1")
379             }
380         }
381         val TEST_TILE_KEY_1 = TileServiceKey(TEST_COMPONENT, TEST_USER_1.identifier)
<lambda>null382         val TEST_DEFAULTS_1 by lazy {
383             CustomTileDefaults.Result(TEST_TILE_1.icon, TEST_TILE_1.label)
384         }
385 
386         val TEST_USER_2 = UserHandle.of(2)!!
<lambda>null387         val TEST_TILE_2 by lazy {
388             Tile().apply {
389                 label = "test_tile_2"
390                 icon = Icon.createWithContentUri("file://test_2")
391             }
392         }
393         val TEST_TILE_KEY_2 = TileServiceKey(TEST_COMPONENT, TEST_USER_2.identifier)
<lambda>null394         val TEST_DEFAULTS_2 by lazy {
395             CustomTileDefaults.Result(TEST_TILE_2.icon, TEST_TILE_2.label)
396         }
397     }
398 }
399