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.pipeline.domain.interactor
18 
19 import android.content.ComponentName
20 import android.content.Context
21 import android.content.Intent
22 import android.content.pm.UserInfo
23 import android.os.UserHandle
24 import android.service.quicksettings.Tile
25 import android.testing.AndroidTestingRunner
26 import androidx.test.filters.SmallTest
27 import com.android.systemui.SysuiTestCase
28 import com.android.systemui.coroutines.collectLastValue
29 import com.android.systemui.dump.nano.SystemUIProtoDump
30 import com.android.systemui.flags.FakeFeatureFlags
31 import com.android.systemui.flags.Flags
32 import com.android.systemui.plugins.qs.QSTile
33 import com.android.systemui.plugins.qs.QSTile.BooleanState
34 import com.android.systemui.qs.FakeQSFactory
35 import com.android.systemui.qs.external.CustomTile
36 import com.android.systemui.qs.external.CustomTileStatePersister
37 import com.android.systemui.qs.external.TileLifecycleManager
38 import com.android.systemui.qs.external.TileServiceKey
39 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
40 import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
41 import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository
42 import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
43 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
44 import com.android.systemui.qs.pipeline.domain.model.TileModel
45 import com.android.systemui.qs.pipeline.shared.TileSpec
46 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
47 import com.android.systemui.qs.toProto
48 import com.android.systemui.settings.UserTracker
49 import com.android.systemui.user.data.repository.FakeUserRepository
50 import com.android.systemui.util.mockito.any
51 import com.android.systemui.util.mockito.mock
52 import com.android.systemui.util.mockito.whenever
53 import com.google.common.truth.Truth.assertThat
54 import com.google.protobuf.nano.MessageNano
55 import kotlinx.coroutines.ExperimentalCoroutinesApi
56 import kotlinx.coroutines.test.StandardTestDispatcher
57 import kotlinx.coroutines.test.TestScope
58 import kotlinx.coroutines.test.runCurrent
59 import kotlinx.coroutines.test.runTest
60 import org.junit.Before
61 import org.junit.Test
62 import org.junit.runner.RunWith
63 import org.mockito.ArgumentMatchers.anyString
64 import org.mockito.Mock
65 import org.mockito.Mockito.inOrder
66 import org.mockito.Mockito.never
67 import org.mockito.Mockito.verify
68 import org.mockito.MockitoAnnotations
69 
70 @SmallTest
71 @RunWith(AndroidTestingRunner::class)
72 @OptIn(ExperimentalCoroutinesApi::class)
73 class CurrentTilesInteractorImplTest : SysuiTestCase() {
74 
75     private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
76     private val userRepository = FakeUserRepository()
77     private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
78     private val tileFactory = FakeQSFactory(::tileCreator)
79     private val customTileAddedRepository: CustomTileAddedRepository =
80         FakeCustomTileAddedRepository()
81     private val featureFlags = FakeFeatureFlags()
82     private val tileLifecycleManagerFactory = TLMFactory()
83 
84     @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
85 
86     @Mock private lateinit var userTracker: UserTracker
87 
88     @Mock private lateinit var logger: QSPipelineLogger
89 
90     private val testDispatcher = StandardTestDispatcher()
91     private val testScope = TestScope(testDispatcher)
92 
93     private val unavailableTiles = mutableSetOf("e")
94 
95     private lateinit var underTest: CurrentTilesInteractorImpl
96 
97     @OptIn(ExperimentalCoroutinesApi::class)
98     @Before
99     fun setup() {
100         MockitoAnnotations.initMocks(this)
101 
102         featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
103         featureFlags.set(Flags.QS_PIPELINE_AUTO_ADD, true)
104 
105         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
106 
107         setUserTracker(0)
108 
109         underTest =
110             CurrentTilesInteractorImpl(
111                 tileSpecRepository = tileSpecRepository,
112                 installedTilesComponentRepository = installedTilesPackageRepository,
113                 userRepository = userRepository,
114                 customTileStatePersister = customTileStatePersister,
115                 tileFactory = tileFactory,
116                 customTileAddedRepository = customTileAddedRepository,
117                 tileLifecycleManagerFactory = tileLifecycleManagerFactory,
118                 userTracker = userTracker,
119                 mainDispatcher = testDispatcher,
120                 backgroundDispatcher = testDispatcher,
121                 scope = testScope.backgroundScope,
122                 logger = logger,
123                 featureFlags = featureFlags,
124             )
125     }
126 
127     @Test
128     fun initialState() =
129         testScope.runTest(USER_INFO_0) {
130             assertThat(underTest.currentTiles.value).isEmpty()
131             assertThat(underTest.currentQSTiles).isEmpty()
132             assertThat(underTest.currentTilesSpecs).isEmpty()
133             assertThat(underTest.userId.value).isEqualTo(0)
134             assertThat(underTest.userContext.value.userId).isEqualTo(0)
135         }
136 
137     @Test
138     fun correctTiles() =
139         testScope.runTest(USER_INFO_0) {
140             val tiles by collectLastValue(underTest.currentTiles)
141 
142             val specs =
143                 listOf(
144                     TileSpec.create("a"),
145                     TileSpec.create("e"),
146                     CUSTOM_TILE_SPEC,
147                     TileSpec.create("d"),
148                     TileSpec.create("non_existent")
149                 )
150             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
151 
152             // check each tile
153 
154             // Tile a
155             val tile0 = tiles!![0]
156             assertThat(tile0.spec).isEqualTo(specs[0])
157             assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec)
158             assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java)
159             assertThat(tile0.tile.isAvailable).isTrue()
160 
161             // Tile e is not available and is not in the list
162 
163             // Custom Tile
164             val tile1 = tiles!![1]
165             assertThat(tile1.spec).isEqualTo(specs[2])
166             assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec)
167             assertThat(tile1.tile).isInstanceOf(CustomTile::class.java)
168             assertThat(tile1.tile.isAvailable).isTrue()
169 
170             // Tile d
171             val tile2 = tiles!![2]
172             assertThat(tile2.spec).isEqualTo(specs[3])
173             assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec)
174             assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java)
175             assertThat(tile2.tile.isAvailable).isTrue()
176 
177             // Tile non-existent shouldn't be created. Therefore, only 3 tiles total
178             assertThat(tiles?.size).isEqualTo(3)
179         }
180 
181     @Test
182     fun logTileCreated() =
183         testScope.runTest(USER_INFO_0) {
184             val specs =
185                 listOf(
186                     TileSpec.create("a"),
187                     CUSTOM_TILE_SPEC,
188                 )
189             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
190             runCurrent()
191 
192             specs.forEach { verify(logger).logTileCreated(it) }
193         }
194 
195     @Test
196     fun logTileNotFoundInFactory() =
197         testScope.runTest(USER_INFO_0) {
198             val specs =
199                 listOf(
200                     TileSpec.create("non_existing"),
201                 )
202             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
203             runCurrent()
204 
205             verify(logger, never()).logTileCreated(any())
206             verify(logger).logTileNotFoundInFactory(specs[0])
207         }
208 
209     @Test
210     fun tileNotAvailableDestroyed_logged() =
211         testScope.runTest(USER_INFO_0) {
212             val specs =
213                 listOf(
214                     TileSpec.create("e"),
215                 )
216             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
217             runCurrent()
218 
219             verify(logger, never()).logTileCreated(any())
220             verify(logger)
221                 .logTileDestroyed(
222                     specs[0],
223                     QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE
224                 )
225         }
226 
227     @Test
228     fun someTilesNotValid_repositorySetToDefinitiveList() =
229         testScope.runTest(USER_INFO_0) {
230             val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
231 
232             val specs =
233                 listOf(
234                     TileSpec.create("a"),
235                     TileSpec.create("e"),
236                 )
237             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
238 
239             assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
240         }
241 
242     @Test
243     fun deduplicatedTiles() =
244         testScope.runTest(USER_INFO_0) {
245             val tiles by collectLastValue(underTest.currentTiles)
246 
247             val specs = listOf(TileSpec.create("a"), TileSpec.create("a"))
248 
249             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
250 
251             assertThat(tiles?.size).isEqualTo(1)
252             assertThat(tiles!![0].spec).isEqualTo(specs[0])
253         }
254 
255     @Test
256     fun tilesChange_platformTileNotRecreated() =
257         testScope.runTest(USER_INFO_0) {
258             val tiles by collectLastValue(underTest.currentTiles)
259 
260             val specs =
261                 listOf(
262                     TileSpec.create("a"),
263                 )
264 
265             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
266             val originalTileA = tiles!![0].tile
267 
268             tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
269 
270             assertThat(tiles?.size).isEqualTo(2)
271             assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
272         }
273 
274     @Test
275     fun tileRemovedIsDestroyed() =
276         testScope.runTest(USER_INFO_0) {
277             val tiles by collectLastValue(underTest.currentTiles)
278 
279             val specs = listOf(TileSpec.create("a"), TileSpec.create("c"))
280 
281             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
282             val originalTileC = tiles!![1].tile
283 
284             tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c")))
285 
286             assertThat(tiles?.size).isEqualTo(1)
287             assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a"))
288 
289             assertThat((originalTileC as FakeQSTile).destroyed).isTrue()
290             verify(logger)
291                 .logTileDestroyed(
292                     TileSpec.create("c"),
293                     QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
294                 )
295         }
296 
297     @Test
298     fun tileBecomesNotAvailable_destroyed() =
299         testScope.runTest(USER_INFO_0) {
300             val tiles by collectLastValue(underTest.currentTiles)
301             val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
302 
303             val specs = listOf(TileSpec.create("a"))
304 
305             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
306             val originalTileA = tiles!![0].tile
307 
308             // Tile becomes unavailable
309             (originalTileA as FakeQSTile).available = false
310             unavailableTiles.add("a")
311             // and there is some change in the specs
312             tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
313             runCurrent()
314 
315             assertThat(originalTileA.destroyed).isTrue()
316             verify(logger)
317                 .logTileDestroyed(
318                     TileSpec.create("a"),
319                     QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
320                 )
321 
322             assertThat(tiles?.size).isEqualTo(1)
323             assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b"))
324             assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA)
325 
326             assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec))
327         }
328 
329     @Test
330     fun userChange_tilesChange() =
331         testScope.runTest(USER_INFO_0) {
332             val tiles by collectLastValue(underTest.currentTiles)
333 
334             val specs0 = listOf(TileSpec.create("a"))
335             val specs1 = listOf(TileSpec.create("b"))
336             tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
337             tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
338 
339             switchUser(USER_INFO_1)
340 
341             assertThat(tiles!![0].spec).isEqualTo(specs1[0])
342             assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec)
343         }
344 
345     @Test
346     fun tileNotPresentInSecondaryUser_destroyedInUserChange() =
347         testScope.runTest(USER_INFO_0) {
348             val tiles by collectLastValue(underTest.currentTiles)
349 
350             val specs0 = listOf(TileSpec.create("a"))
351             val specs1 = listOf(TileSpec.create("b"))
352             tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
353             tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
354 
355             val originalTileA = tiles!![0].tile
356 
357             switchUser(USER_INFO_1)
358             runCurrent()
359 
360             assertThat((originalTileA as FakeQSTile).destroyed).isTrue()
361             verify(logger)
362                 .logTileDestroyed(
363                     specs0[0],
364                     QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER
365                 )
366         }
367 
368     @Test
369     fun userChange_customTileDestroyed_lifecycleNotTerminated() {
370         testScope.runTest(USER_INFO_0) {
371             val tiles by collectLastValue(underTest.currentTiles)
372 
373             val specs = listOf(CUSTOM_TILE_SPEC)
374             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
375             tileSpecRepository.setTiles(USER_INFO_1.id, specs)
376 
377             val originalCustomTile = tiles!![0].tile
378 
379             switchUser(USER_INFO_1)
380             runCurrent()
381 
382             verify(originalCustomTile).destroy()
383             assertThat(tileLifecycleManagerFactory.created).isEmpty()
384         }
385     }
386 
387     @Test
388     fun userChange_sameTileUserChanged() =
389         testScope.runTest(USER_INFO_0) {
390             val tiles by collectLastValue(underTest.currentTiles)
391 
392             val specs = listOf(TileSpec.create("a"))
393             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
394             tileSpecRepository.setTiles(USER_INFO_1.id, specs)
395 
396             val originalTileA = tiles!![0].tile as FakeQSTile
397             assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id)
398 
399             switchUser(USER_INFO_1)
400             runCurrent()
401 
402             assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
403             assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id)
404             verify(logger).logTileUserChanged(specs[0], USER_INFO_1.id)
405         }
406 
407     @Test
408     fun addTile() =
409         testScope.runTest(USER_INFO_0) {
410             val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
411             val spec = TileSpec.create("a")
412             val currentSpecs =
413                 listOf(
414                     TileSpec.create("b"),
415                     TileSpec.create("c"),
416                 )
417             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
418 
419             underTest.addTile(spec, position = 1)
420 
421             val expectedSpecs =
422                 listOf(
423                     TileSpec.create("b"),
424                     spec,
425                     TileSpec.create("c"),
426                 )
427             assertThat(tiles).isEqualTo(expectedSpecs)
428         }
429 
430     @Test
431     fun addTile_currentUser() =
432         testScope.runTest(USER_INFO_1) {
433             val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
434             val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
435             val spec = TileSpec.create("a")
436             val currentSpecs =
437                 listOf(
438                     TileSpec.create("b"),
439                     TileSpec.create("c"),
440                 )
441             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
442             tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
443 
444             switchUser(USER_INFO_1)
445             underTest.addTile(spec, position = 1)
446 
447             assertThat(tiles0).isEqualTo(currentSpecs)
448 
449             val expectedSpecs =
450                 listOf(
451                     TileSpec.create("b"),
452                     spec,
453                     TileSpec.create("c"),
454                 )
455             assertThat(tiles1).isEqualTo(expectedSpecs)
456         }
457 
458     @Test
459     fun removeTile_platform() =
460         testScope.runTest(USER_INFO_0) {
461             val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
462 
463             val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
464             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
465             runCurrent()
466 
467             underTest.removeTiles(specs.subList(0, 1))
468 
469             assertThat(tiles).isEqualTo(specs.subList(1, 2))
470         }
471 
472     @Test
473     fun removeTile_customTile_lifecycleEnded() {
474         testScope.runTest(USER_INFO_0) {
475             val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
476 
477             val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
478             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
479             runCurrent()
480             assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
481                 .isTrue()
482 
483             underTest.removeTiles(listOf(CUSTOM_TILE_SPEC))
484 
485             assertThat(tiles).isEqualTo(specs.subList(0, 1))
486 
487             val tileLifecycleManager =
488                 tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]
489             assertThat(tileLifecycleManager).isNotNull()
490 
491             with(inOrder(tileLifecycleManager!!)) {
492                 verify(tileLifecycleManager).onStopListening()
493                 verify(tileLifecycleManager).onTileRemoved()
494                 verify(tileLifecycleManager).flushMessagesAndUnbind()
495             }
496             assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
497                 .isFalse()
498             verify(customTileStatePersister)
499                 .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
500         }
501     }
502 
503     @Test
504     fun removeTiles_currentUser() =
505         testScope.runTest {
506             val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
507             val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
508             val currentSpecs =
509                 listOf(
510                     TileSpec.create("a"),
511                     TileSpec.create("b"),
512                     TileSpec.create("c"),
513                 )
514             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
515             tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
516 
517             switchUser(USER_INFO_1)
518             runCurrent()
519 
520             underTest.removeTiles(currentSpecs.subList(0, 2))
521 
522             assertThat(tiles0).isEqualTo(currentSpecs)
523             assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3))
524         }
525 
526     @Test
527     fun setTiles() =
528         testScope.runTest(USER_INFO_0) {
529             val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
530 
531             val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b"))
532             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
533             runCurrent()
534 
535             val newSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a"))
536             underTest.setTiles(newSpecs)
537             runCurrent()
538 
539             assertThat(tiles).isEqualTo(newSpecs)
540         }
541 
542     @Test
543     fun setTiles_customTiles_lifecycleEndedIfGone() =
544         testScope.runTest(USER_INFO_0) {
545             val otherCustomTileSpec = TileSpec.create("custom(b/c)")
546 
547             val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec)
548             tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
549             runCurrent()
550 
551             val newSpecs =
552                 listOf(
553                     otherCustomTileSpec,
554                     TileSpec.create("a"),
555                 )
556 
557             underTest.setTiles(newSpecs)
558             runCurrent()
559 
560             val tileLifecycleManager =
561                 tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]!!
562 
563             with(inOrder(tileLifecycleManager)) {
564                 verify(tileLifecycleManager).onStopListening()
565                 verify(tileLifecycleManager).onTileRemoved()
566                 verify(tileLifecycleManager).flushMessagesAndUnbind()
567             }
568             assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
569                 .isFalse()
570             verify(customTileStatePersister)
571                 .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
572         }
573 
574     @Test
575     fun protoDump() =
576         testScope.runTest(USER_INFO_0) {
577             val tiles by collectLastValue(underTest.currentTiles)
578             val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
579 
580             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
581 
582             val stateA = tiles!![0].tile.state
583             stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA")
584             val stateCustom = QSTile.BooleanState()
585             stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB")
586             stateCustom.spec = CUSTOM_TILE_SPEC.spec
587             whenever(tiles!![1].tile.state).thenReturn(stateCustom)
588 
589             val proto = SystemUIProtoDump()
590             underTest.dumpProto(proto, emptyArray())
591 
592             assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue()
593             assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto()))
594                 .isTrue()
595         }
596 
597     @Test
598     fun retainedTiles_callbackNotRemoved() =
599         testScope.runTest(USER_INFO_0) {
600             val tiles by collectLastValue(underTest.currentTiles)
601             tileSpecRepository.setTiles(USER_INFO_0.id, listOf(TileSpec.create("a")))
602 
603             val tileA = tiles!![0].tile
604             val callback = mock<QSTile.Callback>()
605             tileA.addCallback(callback)
606 
607             tileSpecRepository.setTiles(
608                 USER_INFO_0.id,
609                 listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
610             )
611             val newTileA = tiles!![0].tile
612             assertThat(tileA).isSameInstanceAs(newTileA)
613 
614             assertThat((tileA as FakeQSTile).callbacks).containsExactly(callback)
615         }
616 
617     @Test
618     fun packageNotInstalled_customTileNotVisible() =
619         testScope.runTest(USER_INFO_0) {
620             installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
621 
622             val tiles by collectLastValue(underTest.currentTiles)
623 
624             val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
625             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
626 
627             assertThat(tiles!!.size).isEqualTo(1)
628             assertThat(tiles!![0].spec).isEqualTo(specs[0])
629         }
630 
631     @Test
632     fun packageInstalledLater_customTileAdded() =
633         testScope.runTest(USER_INFO_0) {
634             installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet())
635 
636             val tiles by collectLastValue(underTest.currentTiles)
637             val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC, TileSpec.create("b"))
638             tileSpecRepository.setTiles(USER_INFO_0.id, specs)
639 
640             assertThat(tiles!!.size).isEqualTo(2)
641 
642             installedTilesPackageRepository.setInstalledPackagesForUser(
643                 USER_INFO_0.id,
644                 setOf(TEST_COMPONENT)
645             )
646 
647             assertThat(tiles!!.size).isEqualTo(3)
648             assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
649         }
650 
651     private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
652         this.state = state
653         this.label = label
654         this.secondaryLabel = secondaryLabel
655         if (this is BooleanState) {
656             value = state == Tile.STATE_ACTIVE
657         }
658     }
659 
660     private fun tileCreator(spec: String): QSTile? {
661         val currentUser = userTracker.userId
662         return when (spec) {
663             CUSTOM_TILE_SPEC.spec ->
664                 mock<CustomTile> {
665                     var tileSpecReference: String? = null
666                     whenever(user).thenReturn(currentUser)
667                     whenever(component).thenReturn(CUSTOM_TILE_SPEC.componentName)
668                     whenever(isAvailable).thenReturn(true)
669                     whenever(setTileSpec(anyString())).thenAnswer {
670                         tileSpecReference = it.arguments[0] as? String
671                         Unit
672                     }
673                     whenever(tileSpec).thenAnswer { tileSpecReference }
674                     // Also, add it to the set of added tiles (as this happens as part of the tile
675                     // creation).
676                     customTileAddedRepository.setTileAdded(
677                         CUSTOM_TILE_SPEC.componentName,
678                         currentUser,
679                         true
680                     )
681                 }
682             in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles)
683             else -> null
684         }
685     }
686 
687     private fun TestScope.runTest(user: UserInfo, body: suspend TestScope.() -> Unit) {
688         return runTest {
689             switchUser(user)
690             body()
691         }
692     }
693 
694     private suspend fun switchUser(user: UserInfo) {
695         setUserTracker(user.id)
696         installedTilesPackageRepository.setInstalledPackagesForUser(user.id, setOf(TEST_COMPONENT))
697         userRepository.setSelectedUserInfo(user)
698     }
699 
700     private fun setUserTracker(user: Int) {
701         val mockContext = mockUserContext(user)
702         whenever(userTracker.userContext).thenReturn(mockContext)
703         whenever(userTracker.userId).thenReturn(user)
704     }
705 
706     private class TLMFactory : TileLifecycleManager.Factory {
707 
708         val created = mutableMapOf<Pair<Int, ComponentName>, TileLifecycleManager>()
709 
710         override fun create(intent: Intent, userHandle: UserHandle): TileLifecycleManager {
711             val componentName = intent.component!!
712             val user = userHandle.identifier
713             val manager: TileLifecycleManager = mock()
714             created[user to componentName] = manager
715             return manager
716         }
717     }
718 
719     private fun mockUserContext(user: Int): Context {
720         return mock {
721             whenever(this.userId).thenReturn(user)
722             whenever(this.user).thenReturn(UserHandle.of(user))
723         }
724     }
725 
726     companion object {
727         private val USER_INFO_0 = UserInfo().apply { id = 0 }
728         private val USER_INFO_1 = UserInfo().apply { id = 1 }
729 
730         private val VALID_TILES = setOf("a", "b", "c", "d", "e")
731         private val TEST_COMPONENT = ComponentName("pkg", "cls")
732         private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT)
733     }
734 }
735