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