Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 528156d7 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge "Fix resetTiles not marking CustomTile as removed" into main

parents 02785fcd 889716ad
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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
    }
}
+17 −1
Original line number Diff line number Diff line
@@ -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()
    }
+86 −1
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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() =
@@ -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()
@@ -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()
@@ -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()
@@ -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"))
@@ -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"))
@@ -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)
@@ -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 =
@@ -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 =
@@ -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()
@@ -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
+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!")
        }
    }
}
+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