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

Commit ff50b586 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Remove TileServiceManager dependency from CustomTileInteractor" into main

parents c9d6d894 98ff6e0c
Loading
Loading
Loading
Loading
+177 −108
Original line number Diff line number Diff line
@@ -24,16 +24,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.external.FakeCustomTileStatePersister
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.commons.copy
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -44,22 +47,23 @@ import org.junit.runner.RunWith
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileRepositoryTest : SysuiTestCase() {

    private val testScope = TestScope()

    private val persister = FakeCustomTileStatePersister()

    private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }
    private val underTest: CustomTileRepository =
        with(kosmos) {
            CustomTileRepositoryImpl(
            TileSpec.create(TEST_COMPONENT),
            persister,
                tileSpec,
                customTileStatePersister,
                packageManagerAdapterFacade.packageManagerAdapter,
                testScope.testScheduler,
            )
        }

    @Test
    fun persistableTileIsRestoredForUser() =
        with(kosmos) {
            testScope.runTest {
            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
            persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
                customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
                customTileStatePersister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)

                underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
                runCurrent()
@@ -67,11 +71,13 @@ class CustomTileRepositoryTest : SysuiTestCase() {
                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
            }
        }

    @Test
    fun notPersistableTileIsNotRestored() =
        with(kosmos) {
            testScope.runTest {
            persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
                customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
                val tiles = collectValues(underTest.getTiles(TEST_USER_1))

                underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
@@ -79,9 +85,11 @@ class CustomTileRepositoryTest : SysuiTestCase() {

                assertThat(tiles()).isEmpty()
            }
        }

    @Test
    fun emptyPersistedStateIsHandled() =
        with(kosmos) {
            testScope.runTest {
                val tiles = collectValues(underTest.getTiles(TEST_USER_1))

@@ -90,27 +98,34 @@ class CustomTileRepositoryTest : SysuiTestCase() {

                assertThat(tiles()).isEmpty()
            }
        }

    @Test
    fun updatingWithPersistableTilePersists() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
                runCurrent()

            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
                    .isEqualTo(TEST_TILE_1)
            }
        }

    @Test
    fun updatingWithNotPersistableTileDoesntPersist() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
                runCurrent()

            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
            }
        }

    @Test
    fun updateWithTileEmits() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
                runCurrent()
@@ -118,36 +133,45 @@ class CustomTileRepositoryTest : SysuiTestCase() {
                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
            }
        }

    @Test
    fun updatingPeristableWithDefaultsPersists() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
                runCurrent()

            assertThat(persister.readState(TEST_TILE_KEY_1)).isEqualTo(TEST_TILE_1)
                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1))
                    .isEqualTo(TEST_TILE_1)
            }
        }

    @Test
    fun updatingNotPersistableWithDefaultsDoesntPersist() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
                runCurrent()

            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
            }
        }

    @Test
    fun updatingPeristableWithErrorDefaultsDoesntPersist() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
                runCurrent()

            assertThat(persister.readState(TEST_TILE_KEY_1)).isNull()
                assertThat(customTileStatePersister.readState(TEST_TILE_KEY_1)).isNull()
            }
        }

    @Test
    fun updateWithDefaultsEmits() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
                runCurrent()
@@ -155,18 +179,22 @@ class CustomTileRepositoryTest : SysuiTestCase() {
                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
            }
        }

    @Test
    fun getTileForAnotherUserReturnsNull() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
                runCurrent()

                assertThat(underTest.getTile(TEST_USER_2)).isNull()
            }
        }

    @Test
    fun getTilesForAnotherUserEmpty() =
        with(kosmos) {
            testScope.runTest {
                val tiles = collectValues(underTest.getTiles(TEST_USER_2))

@@ -175,23 +203,31 @@ class CustomTileRepositoryTest : SysuiTestCase() {

                assertThat(tiles()).isEmpty()
            }
        }

    @Test
    fun updatingWithTileForTheSameUserAddsData() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
                runCurrent()

            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
                underTest.updateWithTile(
                    TEST_USER_1,
                    Tile().apply { subtitle = "test_subtitle" },
                    true
                )
                runCurrent()

                val expectedTile = TEST_TILE_1.copy().apply { subtitle = "test_subtitle" }
                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
            }
        }

    @Test
    fun updatingWithTileForAnotherUserOverridesTile() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
                runCurrent()
@@ -204,11 +240,17 @@ class CustomTileRepositoryTest : SysuiTestCase() {
                assertThat(tiles()).hasSize(1)
                assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
            }
        }

    @Test
    fun updatingWithDefaultsForTheSameUserAddsData() =
        with(kosmos) {
            testScope.runTest {
            underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
                underTest.updateWithTile(
                    TEST_USER_1,
                    Tile().apply { subtitle = "test_subtitle" },
                    true
                )
                runCurrent()

                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
@@ -218,9 +260,11 @@ class CustomTileRepositoryTest : SysuiTestCase() {
                assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(expectedTile)
                assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(expectedTile)
            }
        }

    @Test
    fun updatingWithDefaultsForAnotherUserOverridesTile() =
        with(kosmos) {
            testScope.runTest {
                underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
                runCurrent()
@@ -233,6 +277,31 @@ class CustomTileRepositoryTest : SysuiTestCase() {
                assertThat(tiles()).hasSize(1)
                assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
            }
        }

    @Test
    fun isActiveFollowsPackageManagerAdapter() =
        with(kosmos) {
            testScope.runTest {
                packageManagerAdapterFacade.setIsActive(false)
                assertThat(underTest.isTileActive()).isFalse()

                packageManagerAdapterFacade.setIsActive(true)
                assertThat(underTest.isTileActive()).isTrue()
            }
        }

    @Test
    fun isToggleableFollowsPackageManagerAdapter() =
        with(kosmos) {
            testScope.runTest {
                packageManagerAdapterFacade.setIsToggleable(false)
                assertThat(underTest.isTileToggleable()).isFalse()

                packageManagerAdapterFacade.setIsToggleable(true)
                assertThat(underTest.isTileToggleable()).isTrue()
            }
        }

    private companion object {

+99 −97
Original line number Diff line number Diff line
@@ -25,59 +25,38 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.qs.external.FakeCustomTileStatePersister
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat
import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.customTileRepository
import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
import com.android.systemui.util.mockito.whenever
import com.android.systemui.qs.tiles.impl.custom.tileSpec
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class CustomTileInteractorTest : SysuiTestCase() {

    @Mock private lateinit var tileServiceManager: TileServiceManager
    private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) }

    private val testScope = TestScope()

    private val defaultsRepository = FakeCustomTileDefaultsRepository()
    private val customTileStatePersister = FakeCustomTileStatePersister()
    private val customTileRepository =
        FakeCustomTileRepository(
            TEST_TILE_SPEC,
            customTileStatePersister,
            testScope.testScheduler,
        )

    private lateinit var underTest: CustomTileInteractor

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)

        underTest =
    private val underTest: CustomTileInteractor =
        with(kosmos) {
            CustomTileInteractor(
                TEST_USER,
                defaultsRepository,
                customTileDefaultsRepository,
                customTileRepository,
                tileServiceManager,
                testScope.backgroundScope,
                testScope.testScheduler,
            )
@@ -85,29 +64,32 @@ class CustomTileInteractorTest : SysuiTestCase() {

    @Test
    fun activeTileIsAvailableAfterRestored() =
        with(kosmos) {
            testScope.runTest {
            whenever(tileServiceManager.isActiveTile).thenReturn(true)
                customTileRepository.setTileActive(true)
                customTileStatePersister.persistState(
                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
                    TEST_TILE,
                )

            underTest.init()
                underTest.initForUser(TEST_USER)

            assertThat(underTest.tile).isEqualTo(TEST_TILE)
            assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
                assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
                assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
            }
        }

    @Test
    fun notActiveTileIsAvailableAfterUpdated() =
        with(kosmos) {
            testScope.runTest {
            whenever(tileServiceManager.isActiveTile).thenReturn(false)
                customTileRepository.setTileActive(false)
                customTileStatePersister.persistState(
                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
                    TEST_TILE,
                )
            val tiles = collectValues(underTest.tiles)
            val initJob = launch { underTest.init() }
                val tiles = collectValues(underTest.getTiles(TEST_USER))
                val initJob = launch { underTest.initForUser(TEST_USER) }

                underTest.updateTile(TEST_TILE)
                runCurrent()
@@ -116,66 +98,86 @@ class CustomTileInteractorTest : SysuiTestCase() {
                assertThat(tiles()).hasSize(1)
                assertThat(tiles().last()).isEqualTo(TEST_TILE)
            }
        }

    @Test
    fun notActiveTileIsAvailableAfterDefaultsUpdated() =
        with(kosmos) {
            testScope.runTest {
            whenever(tileServiceManager.isActiveTile).thenReturn(false)
                customTileRepository.setTileActive(false)
                customTileStatePersister.persistState(
                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
                    TEST_TILE,
                )
            val tiles = collectValues(underTest.tiles)
            val initJob = launch { underTest.init() }
                val tiles = collectValues(underTest.getTiles(TEST_USER))
                val initJob = launch { underTest.initForUser(TEST_USER) }

            defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
            defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
                customTileDefaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
                customTileDefaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
                runCurrent()
                initJob.join()

                assertThat(tiles()).hasSize(1)
                assertThat(tiles().last()).isEqualTo(TEST_TILE)
            }
        }

    @Test(expected = IllegalStateException::class)
    fun getTileBeforeInitThrows() = testScope.runTest { underTest.tile }
    fun getTileBeforeInitThrows() =
        with(kosmos) { testScope.runTest { underTest.getTile(TEST_USER) } }

    @Test
    fun initSuspendsForActiveTileNotRestoredAndNotUpdated() =
        with(kosmos) {
            testScope.runTest {
            whenever(tileServiceManager.isActiveTile).thenReturn(true)
            val tiles = collectValues(underTest.tiles)
                customTileRepository.setTileActive(true)
                val tiles = collectValues(underTest.getTiles(TEST_USER))

            val initJob = backgroundScope.launch { underTest.init() }
                val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
                advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)

                // Is still suspended
                assertThat(initJob.isActive).isTrue()
                assertThat(tiles()).isEmpty()
            }
        }

    @Test
    fun initSuspendedForNotActiveTileWithoutUpdates() =
        with(kosmos) {
            testScope.runTest {
            whenever(tileServiceManager.isActiveTile).thenReturn(false)
                customTileRepository.setTileActive(false)
                customTileStatePersister.persistState(
                    TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
                    TEST_TILE,
                )
            val tiles = collectValues(underTest.tiles)
                val tiles = collectValues(underTest.getTiles(TEST_USER))

            val initJob = backgroundScope.launch { underTest.init() }
                val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
                advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)

                // Is still suspended
                assertThat(initJob.isActive).isTrue()
                assertThat(tiles()).isEmpty()
            }
        }

    @Test
    fun toggleableFollowsTheRepository() {
        with(kosmos) {
            testScope.runTest {
                customTileRepository.setTileToggleable(false)
                assertThat(underTest.isTileToggleable()).isFalse()

                customTileRepository.setTileToggleable(true)
                assertThat(underTest.isTileToggleable()).isTrue()
            }
        }
    }

    private companion object {

        val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
        val TEST_TILE_SPEC = TileSpec.create(TEST_COMPONENT)
        val TEST_USER = UserHandle.of(1)!!
        val TEST_TILE =
            Tile().apply {
+47 −0
Original line number Diff line number Diff line
@@ -16,11 +16,15 @@

package com.android.systemui.qs.tiles.impl.custom.data.repository

import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.graphics.drawable.Icon
import android.os.UserHandle
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.external.CustomTileStatePersister
import com.android.systemui.qs.external.PackageManagerAdapter
import com.android.systemui.qs.external.TileServiceKey
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.commons.copy
@@ -63,6 +67,12 @@ interface CustomTileRepository {
     */
    fun getTile(user: UserHandle): Tile?

    /** @see [com.android.systemui.qs.external.TileLifecycleManager.isActiveTile] */
    suspend fun isTileActive(): Boolean

    /** @see [com.android.systemui.qs.external.TileLifecycleManager.isToggleableTile] */
    suspend fun isTileToggleable(): Boolean

    /**
     * Updates tile with the non-null values from [newTile]. Overwrites the current cache when
     * [user] differs from the cached one. [isPersistable] tile will be persisted to be possibly
@@ -92,6 +102,7 @@ class CustomTileRepositoryImpl
constructor(
    private val tileSpec: TileSpec.CustomTileSpec,
    private val customTileStatePersister: CustomTileStatePersister,
    private val packageManagerAdapter: PackageManagerAdapter,
    @Background private val backgroundContext: CoroutineContext,
) : CustomTileRepository {

@@ -149,6 +160,34 @@ constructor(
        }
    }

    override suspend fun isTileActive(): Boolean =
        withContext(backgroundContext) {
            try {
                val info: ServiceInfo =
                    packageManagerAdapter.getServiceInfo(
                        tileSpec.componentName,
                        META_DATA_QUERY_FLAGS
                    )
                info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
            } catch (e: PackageManager.NameNotFoundException) {
                false
            }
        }

    override suspend fun isTileToggleable(): Boolean =
        withContext(backgroundContext) {
            try {
                val info: ServiceInfo =
                    packageManagerAdapter.getServiceInfo(
                        tileSpec.componentName,
                        META_DATA_QUERY_FLAGS
                    )
                info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
            } catch (e: PackageManager.NameNotFoundException) {
                false
            }
        }

    private suspend fun updateTile(
        user: UserHandle,
        isPersistable: Boolean,
@@ -193,4 +232,12 @@ constructor(
    private fun UserHandle.getKey() = TileServiceKey(tileSpec.componentName, this.identifier)

    private data class TileWithUser(val user: UserHandle, val tile: Tile)

    private companion object {
        const val META_DATA_QUERY_FLAGS =
            (PackageManager.GET_META_DATA or
                PackageManager.MATCH_UNINSTALLED_PACKAGES or
                PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
                PackageManager.MATCH_DIRECT_BOOT_AWARE)
    }
}
+20 −20

File changed.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -22,8 +22,8 @@ import kotlinx.coroutines.flow.StateFlow

/**
 * Represents tiles behaviour logic. This ViewModel is a connection between tile view and data
 * layers. All direct inheritors must be added to the [QSTileViewModelInterfaceComplianceTest] class
 * to pass compliance tests.
 * layers. All direct inheritors must be added to the
 * [com.android.systemui.qs.tiles.viewmodel.QSTileViewModelTest] class to pass interface tests.
 *
 * All methods of this view model should be considered running on the main thread. This means no
 * synchronous long running operations are permitted in any method.
Loading