Loading packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -2116,3 +2116,13 @@ flag { purpose: PURPOSE_BUGFIX } } flag { name: "reset_tiles_removes_custom_tiles" namespace: "systemui" description: "Fixes bug where calling Reset Tiles will not properly mark custom tiles as removed." bug: "431235255" metadata { purpose: PURPOSE_BUGFIX } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt +17 −1 Original line number Diff line number Diff line Loading @@ -122,13 +122,29 @@ class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() { assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 1)).isTrue() } @Test fun removeAllNonCurrent() { val userId = 0 val sharedPrefs = FakeSharedPreferences() val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs)) underTest = CustomTileAddedSharedPrefsRepository(userFileManager) underTest.setTileAdded(TEST_COMPONENT, userId, true) underTest.setTileAdded(OTHER_TEST_COMPONENT, userId, true) underTest.removeNonCurrentTiles(currentTiles = listOf(OTHER_TEST_COMPONENT), userId) assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse() assertThat(underTest.isTileAdded(OTHER_TEST_COMPONENT, userId)).isTrue() } private fun SharedPreferences.getForComponentName(componentName: ComponentName): Boolean { return getBoolean(componentName.flattenToString(), false) } private fun SharedPreferences.setForComponentName( componentName: ComponentName, value: Boolean value: Boolean, ) { edit().putBoolean(componentName.flattenToString(), value).commit() } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +86 −1 Original line number Diff line number Diff line Loading @@ -20,10 +20,12 @@ import android.content.ComponentName import android.content.Intent import android.content.pm.UserInfo import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.quicksettings.Tile import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_QS_NEW_TILES import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue Loading @@ -41,7 +43,9 @@ import com.android.systemui.qs.external.TileLifecycleManager import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.external.customTileStatePersister import com.android.systemui.qs.external.tileLifecycleManagerFactory import com.android.systemui.qs.pipeline.data.repository.FakeDefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.defaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.domain.model.TileModel Loading Loading @@ -90,7 +94,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private val unavailableTiles = mutableSetOf("e") private val underTest = kosmos.currentTilesInteractor private val Kosmos.underTest by Kosmos.Fixture { kosmos.currentTilesInteractor } @Test fun initialState() = Loading Loading @@ -154,6 +158,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun logTileCreated() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading @@ -166,6 +172,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun logTileNotFoundInFactory() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("non_existing")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading @@ -179,6 +187,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun tileNotAvailableDestroyed_logged() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("e")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading @@ -196,6 +206,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun someTilesNotValid_repositorySetToDefinitiveList() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val specs = listOf(TileSpec.create("a"), TileSpec.create("e")) Loading Loading @@ -423,6 +435,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun removeTile_platform() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val specs = listOf(TileSpec.create("a"), TileSpec.create("b")) Loading @@ -439,6 +453,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun removeTile_customTile_lifecycleEnded() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) Loading Loading @@ -476,6 +492,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun removeTiles_currentUser() = with(kosmos) { testScope.runTest { underTest // Instantiation val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) val currentSpecs = Loading Loading @@ -516,6 +534,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun setTiles_customTiles_lifecycleEndedIfGone() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val otherCustomTileSpec = TileSpec.create("custom(b/c)") val currentSpecs = Loading Loading @@ -655,6 +675,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun changeInPackagesTiles_doesntTriggerUserChange_logged() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("a")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading Loading @@ -726,6 +748,69 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { } } @Test fun resetTiles_default() = with(kosmos) { testScope.runTest(USER_INFO_0) { val default = listOf(TileSpec.create("a"), TileSpec.create("b")) defaultTilesRepository = FakeDefaultTilesRepository(default) val tiles by collectLastValue(underTest.currentTiles) underTest.setTiles(listOf(TileSpec.create("c"), TileSpec.create("d"))) runCurrent() underTest.resetTiles() runCurrent() assertThat(tiles!!.map { it.spec }).isEqualTo(default) } } @Test @DisableFlags(Flags.FLAG_RESET_TILES_REMOVES_CUSTOM_TILES) fun resetTiles_flagDisabled_customTileNotMarkedAsRemoved() = with(kosmos) { testScope.runTest(USER_INFO_0) { val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a")) underTest.setTiles(currentSpecs) runCurrent() underTest.resetTiles() runCurrent() assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) .isTrue() } } @Test @EnableFlags(Flags.FLAG_RESET_TILES_REMOVES_CUSTOM_TILES) fun resetTiles_flagEnabled_customTileNotMarkedAsRemoved() = with(kosmos) { testScope.runTest(USER_INFO_0) { val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a")) underTest.setTiles(currentSpecs) runCurrent() underTest.resetTiles() runCurrent() assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) .isFalse() val tileLifecycleManager = (tileLifecycleManagerFactory as TLMFactory) .created[USER_INFO_0.id to TEST_COMPONENT]!! with(inOrder(tileLifecycleManager)) { verify(tileLifecycleManager).onStopListening() verify(tileLifecycleManager).onTileRemoved() verify(tileLifecycleManager).flushMessagesAndUnbind() } } } private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { this.state = state this.label = label Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/upgrade/CustomTileAddedRepositoryUpgraderTest.kt 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.qs.pipeline.domain.upgrade import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.advanceTimeBy import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class CustomTileAddedRepositoryUpgraderTest : SysuiTestCase() { private val kosmos = testKosmos() private val userFlow = MutableStateFlow(0) private val Kosmos.underTest by Kosmos.Fixture { customTileAddedRepositoryUpgrader } @Test fun noUpgrades_noFailure_repositoryInVersion1() = kosmos.runTest { customTileAddedUpgradeSet = emptySet() underTest.start(userFlow) assertThat(customTileAddedRepository.getVersion(userFlow.value)).isEqualTo(1) } @Test(expected = IllegalStateException::class) fun upgradeSkip_exception() = kosmos.runTest { customTileAddedUpgradeSet = setOf(Optional.of(Upgrader(version = 3))) underTest.start(userFlow) } @Test(expected = IllegalStateException::class) fun repeatedVersions_exception() = kosmos.runTest { customTileAddedUpgradeSet = setOf(Optional.of(Upgrader(version = 2)), Optional.of(Upgrader(version = 2))) underTest.start(userFlow) } @OptIn(ExperimentalCoroutinesApi::class) @Test fun upgradesInOrder() = kosmos.runTest { val upgraderV2 = Upgrader(version = 2) { testDispatcher.scheduler.currentTime } val upgraderV3 = Upgrader(version = 3) { testDispatcher.scheduler.currentTime } customTileAddedUpgradeSet = setOf(Optional.of(upgraderV2), Optional.of(upgraderV3)) underTest.start(userFlow) advanceTimeBy(5.milliseconds) runCurrent() val currentUser = userFlow.value assertThat(upgraderV2.getUpgradesForUser(currentUser).single()) .isLessThan(upgraderV3.getUpgradesForUser(currentUser).single()) assertThat(customTileAddedRepository.getVersion(currentUser)).isEqualTo(3) } @Test fun failingUpgrader_stopsUpgrading() = kosmos.runTest { val upgraderV2 = Upgrader(version = 2) val failingUpgraderV3 = FailingUpgrader(version = 3) customTileAddedUpgradeSet = setOf(Optional.of(upgraderV2), Optional.of(failingUpgraderV3)) underTest.start(userFlow) advanceTimeBy(5.milliseconds) runCurrent() val currentUser = userFlow.value assertThat(upgraderV2.getUpgradesForUser(currentUser)).hasSize(1) assertThat(customTileAddedRepository.getVersion(currentUser)).isEqualTo(2) } @Test fun upgradeOnCurrentUser() = kosmos.runTest { val upgraderV2 = Upgrader(version = 2) { testDispatcher.scheduler.currentTime } val upgraderV3 = Upgrader(version = 3) { testDispatcher.scheduler.currentTime } customTileAddedUpgradeSet = setOf(Optional.of(upgraderV2), Optional.of(upgraderV3)) val oldUser = userFlow.value val newUser = oldUser + 1 underTest.start(userFlow) advanceTimeBy(5.milliseconds) runCurrent() assertThat(customTileAddedRepository.getVersion(newUser)).isEqualTo(1) userFlow.value = newUser advanceTimeBy(5.milliseconds) runCurrent() assertThat(customTileAddedRepository.getVersion(newUser)).isEqualTo(3) } private class Upgrader(override val version: Int, private val time: () -> Long = { 0L }) : CustomTileAddedUpgrade { private val upgradeCalls = mutableMapOf<Int, MutableList<Long>>() override suspend fun CustomTileAddedRepository.upgradeForUser(userId: Int) { upgradeCalls.getOrPut(userId) { mutableListOf() }.add(time()) delay(1.milliseconds) // Delay 1 ms so updates are not concurrent. } fun getUpgradesForUser(userId: Int): List<Long> { return upgradeCalls.getOrDefault(userId, emptyList()) } } private class FailingUpgrader(override val version: Int) : CustomTileAddedUpgrade { override suspend fun CustomTileAddedRepository.upgradeForUser(userId: Int) { throw RuntimeException("Failed!") } } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/upgrade/RemoveAlreadyRemovedTilesTest.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.qs.pipeline.domain.upgrade import android.content.ComponentName import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(Flags.FLAG_RESET_TILES_REMOVES_CUSTOM_TILES) class RemoveAlreadyRemovedTilesTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.underTest: CustomTileAddedUpgrade by Kosmos.Fixture { RemoveAlreadyRemovedTiles(currentTilesInteractor) } @Test fun version() = kosmos.runTest { assertThat(underTest.version).isEqualTo(2) } @Test fun upgrade_requestRemoveComponents() = kosmos.runTest { val currentUser = currentTilesInteractor.userId.value val storedComponents = listOf( TileSpec.create(ComponentName("a", "b")), TileSpec.create(ComponentName("c", "d")), TileSpec.create(ComponentName("e", "f")), ) fakeInstalledTilesRepository.setInstalledPackagesForUser( currentUser, storedComponents.map { it.componentName }.toSet(), ) val currentTiles = listOf(TileSpec.create("a"), storedComponents[0]) currentTilesInteractor.setTiles(currentTiles) // There are stored components that are not part of the current tiles storedComponents.forEach { customTileAddedRepository.setTileAdded(it.componentName, currentUser, added = true) } with(underTest) { customTileAddedRepository.upgradeForUser(currentUser) } assertThat( customTileAddedRepository.isTileAdded( storedComponents[0].componentName, currentUser, ) ) .isTrue() assertThat( customTileAddedRepository.isTileAdded( storedComponents[1].componentName, currentUser, ) ) .isFalse() assertThat( customTileAddedRepository.isTileAdded( storedComponents[2].componentName, currentUser, ) ) .isFalse() } } Loading
packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -2116,3 +2116,13 @@ flag { purpose: PURPOSE_BUGFIX } } flag { name: "reset_tiles_removes_custom_tiles" namespace: "systemui" description: "Fixes bug where calling Reset Tiles will not properly mark custom tiles as removed." bug: "431235255" metadata { purpose: PURPOSE_BUGFIX } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt +17 −1 Original line number Diff line number Diff line Loading @@ -122,13 +122,29 @@ class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() { assertThat(underTest.isTileAdded(TEST_COMPONENT, userId = 1)).isTrue() } @Test fun removeAllNonCurrent() { val userId = 0 val sharedPrefs = FakeSharedPreferences() val userFileManager = FakeUserFileManager(mapOf(userId to sharedPrefs)) underTest = CustomTileAddedSharedPrefsRepository(userFileManager) underTest.setTileAdded(TEST_COMPONENT, userId, true) underTest.setTileAdded(OTHER_TEST_COMPONENT, userId, true) underTest.removeNonCurrentTiles(currentTiles = listOf(OTHER_TEST_COMPONENT), userId) assertThat(underTest.isTileAdded(TEST_COMPONENT, userId)).isFalse() assertThat(underTest.isTileAdded(OTHER_TEST_COMPONENT, userId)).isTrue() } private fun SharedPreferences.getForComponentName(componentName: ComponentName): Boolean { return getBoolean(componentName.flattenToString(), false) } private fun SharedPreferences.setForComponentName( componentName: ComponentName, value: Boolean value: Boolean, ) { edit().putBoolean(componentName.flattenToString(), value).commit() } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +86 −1 Original line number Diff line number Diff line Loading @@ -20,10 +20,12 @@ import android.content.ComponentName import android.content.Intent import android.content.pm.UserInfo import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.quicksettings.Tile import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_QS_NEW_TILES import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue Loading @@ -41,7 +43,9 @@ import com.android.systemui.qs.external.TileLifecycleManager import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.external.customTileStatePersister import com.android.systemui.qs.external.tileLifecycleManagerFactory import com.android.systemui.qs.pipeline.data.repository.FakeDefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.defaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository import com.android.systemui.qs.pipeline.domain.model.TileModel Loading Loading @@ -90,7 +94,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private val unavailableTiles = mutableSetOf("e") private val underTest = kosmos.currentTilesInteractor private val Kosmos.underTest by Kosmos.Fixture { kosmos.currentTilesInteractor } @Test fun initialState() = Loading Loading @@ -154,6 +158,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun logTileCreated() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading @@ -166,6 +172,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun logTileNotFoundInFactory() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("non_existing")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading @@ -179,6 +187,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun tileNotAvailableDestroyed_logged() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("e")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading @@ -196,6 +206,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun someTilesNotValid_repositorySetToDefinitiveList() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val specs = listOf(TileSpec.create("a"), TileSpec.create("e")) Loading Loading @@ -423,6 +435,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun removeTile_platform() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val specs = listOf(TileSpec.create("a"), TileSpec.create("b")) Loading @@ -439,6 +453,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun removeTile_customTile_lifecycleEnded() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC) Loading Loading @@ -476,6 +492,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun removeTiles_currentUser() = with(kosmos) { testScope.runTest { underTest // Instantiation val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id)) val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id)) val currentSpecs = Loading Loading @@ -516,6 +534,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun setTiles_customTiles_lifecycleEndedIfGone() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val otherCustomTileSpec = TileSpec.create("custom(b/c)") val currentSpecs = Loading Loading @@ -655,6 +675,8 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { fun changeInPackagesTiles_doesntTriggerUserChange_logged() = with(kosmos) { testScope.runTest(USER_INFO_0) { underTest // Instantiation val specs = listOf(TileSpec.create("a")) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() Loading Loading @@ -726,6 +748,69 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { } } @Test fun resetTiles_default() = with(kosmos) { testScope.runTest(USER_INFO_0) { val default = listOf(TileSpec.create("a"), TileSpec.create("b")) defaultTilesRepository = FakeDefaultTilesRepository(default) val tiles by collectLastValue(underTest.currentTiles) underTest.setTiles(listOf(TileSpec.create("c"), TileSpec.create("d"))) runCurrent() underTest.resetTiles() runCurrent() assertThat(tiles!!.map { it.spec }).isEqualTo(default) } } @Test @DisableFlags(Flags.FLAG_RESET_TILES_REMOVES_CUSTOM_TILES) fun resetTiles_flagDisabled_customTileNotMarkedAsRemoved() = with(kosmos) { testScope.runTest(USER_INFO_0) { val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a")) underTest.setTiles(currentSpecs) runCurrent() underTest.resetTiles() runCurrent() assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) .isTrue() } } @Test @EnableFlags(Flags.FLAG_RESET_TILES_REMOVES_CUSTOM_TILES) fun resetTiles_flagEnabled_customTileNotMarkedAsRemoved() = with(kosmos) { testScope.runTest(USER_INFO_0) { val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a")) underTest.setTiles(currentSpecs) runCurrent() underTest.resetTiles() runCurrent() assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id)) .isFalse() val tileLifecycleManager = (tileLifecycleManagerFactory as TLMFactory) .created[USER_INFO_0.id to TEST_COMPONENT]!! with(inOrder(tileLifecycleManager)) { verify(tileLifecycleManager).onStopListening() verify(tileLifecycleManager).onTileRemoved() verify(tileLifecycleManager).flushMessagesAndUnbind() } } } private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { this.state = state this.label = label Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/upgrade/CustomTileAddedRepositoryUpgraderTest.kt 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.qs.pipeline.domain.upgrade import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.advanceTimeBy import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testDispatcher import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class CustomTileAddedRepositoryUpgraderTest : SysuiTestCase() { private val kosmos = testKosmos() private val userFlow = MutableStateFlow(0) private val Kosmos.underTest by Kosmos.Fixture { customTileAddedRepositoryUpgrader } @Test fun noUpgrades_noFailure_repositoryInVersion1() = kosmos.runTest { customTileAddedUpgradeSet = emptySet() underTest.start(userFlow) assertThat(customTileAddedRepository.getVersion(userFlow.value)).isEqualTo(1) } @Test(expected = IllegalStateException::class) fun upgradeSkip_exception() = kosmos.runTest { customTileAddedUpgradeSet = setOf(Optional.of(Upgrader(version = 3))) underTest.start(userFlow) } @Test(expected = IllegalStateException::class) fun repeatedVersions_exception() = kosmos.runTest { customTileAddedUpgradeSet = setOf(Optional.of(Upgrader(version = 2)), Optional.of(Upgrader(version = 2))) underTest.start(userFlow) } @OptIn(ExperimentalCoroutinesApi::class) @Test fun upgradesInOrder() = kosmos.runTest { val upgraderV2 = Upgrader(version = 2) { testDispatcher.scheduler.currentTime } val upgraderV3 = Upgrader(version = 3) { testDispatcher.scheduler.currentTime } customTileAddedUpgradeSet = setOf(Optional.of(upgraderV2), Optional.of(upgraderV3)) underTest.start(userFlow) advanceTimeBy(5.milliseconds) runCurrent() val currentUser = userFlow.value assertThat(upgraderV2.getUpgradesForUser(currentUser).single()) .isLessThan(upgraderV3.getUpgradesForUser(currentUser).single()) assertThat(customTileAddedRepository.getVersion(currentUser)).isEqualTo(3) } @Test fun failingUpgrader_stopsUpgrading() = kosmos.runTest { val upgraderV2 = Upgrader(version = 2) val failingUpgraderV3 = FailingUpgrader(version = 3) customTileAddedUpgradeSet = setOf(Optional.of(upgraderV2), Optional.of(failingUpgraderV3)) underTest.start(userFlow) advanceTimeBy(5.milliseconds) runCurrent() val currentUser = userFlow.value assertThat(upgraderV2.getUpgradesForUser(currentUser)).hasSize(1) assertThat(customTileAddedRepository.getVersion(currentUser)).isEqualTo(2) } @Test fun upgradeOnCurrentUser() = kosmos.runTest { val upgraderV2 = Upgrader(version = 2) { testDispatcher.scheduler.currentTime } val upgraderV3 = Upgrader(version = 3) { testDispatcher.scheduler.currentTime } customTileAddedUpgradeSet = setOf(Optional.of(upgraderV2), Optional.of(upgraderV3)) val oldUser = userFlow.value val newUser = oldUser + 1 underTest.start(userFlow) advanceTimeBy(5.milliseconds) runCurrent() assertThat(customTileAddedRepository.getVersion(newUser)).isEqualTo(1) userFlow.value = newUser advanceTimeBy(5.milliseconds) runCurrent() assertThat(customTileAddedRepository.getVersion(newUser)).isEqualTo(3) } private class Upgrader(override val version: Int, private val time: () -> Long = { 0L }) : CustomTileAddedUpgrade { private val upgradeCalls = mutableMapOf<Int, MutableList<Long>>() override suspend fun CustomTileAddedRepository.upgradeForUser(userId: Int) { upgradeCalls.getOrPut(userId) { mutableListOf() }.add(time()) delay(1.milliseconds) // Delay 1 ms so updates are not concurrent. } fun getUpgradesForUser(userId: Int): List<Long> { return upgradeCalls.getOrDefault(userId, emptyList()) } } private class FailingUpgrader(override val version: Int) : CustomTileAddedUpgrade { override suspend fun CustomTileAddedRepository.upgradeForUser(userId: Int) { throw RuntimeException("Failed!") } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/upgrade/RemoveAlreadyRemovedTilesTest.kt 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.qs.pipeline.domain.upgrade import android.content.ComponentName import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(Flags.FLAG_RESET_TILES_REMOVES_CUSTOM_TILES) class RemoveAlreadyRemovedTilesTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.underTest: CustomTileAddedUpgrade by Kosmos.Fixture { RemoveAlreadyRemovedTiles(currentTilesInteractor) } @Test fun version() = kosmos.runTest { assertThat(underTest.version).isEqualTo(2) } @Test fun upgrade_requestRemoveComponents() = kosmos.runTest { val currentUser = currentTilesInteractor.userId.value val storedComponents = listOf( TileSpec.create(ComponentName("a", "b")), TileSpec.create(ComponentName("c", "d")), TileSpec.create(ComponentName("e", "f")), ) fakeInstalledTilesRepository.setInstalledPackagesForUser( currentUser, storedComponents.map { it.componentName }.toSet(), ) val currentTiles = listOf(TileSpec.create("a"), storedComponents[0]) currentTilesInteractor.setTiles(currentTiles) // There are stored components that are not part of the current tiles storedComponents.forEach { customTileAddedRepository.setTileAdded(it.componentName, currentUser, added = true) } with(underTest) { customTileAddedRepository.upgradeForUser(currentUser) } assertThat( customTileAddedRepository.isTileAdded( storedComponents[0].componentName, currentUser, ) ) .isTrue() assertThat( customTileAddedRepository.isTileAdded( storedComponents[1].componentName, currentUser, ) ) .isFalse() assertThat( customTileAddedRepository.isTileAdded( storedComponents[2].componentName, currentUser, ) ) .isFalse() } }