Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt +177 −108 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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) Loading @@ -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)) Loading @@ -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() Loading @@ -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() Loading @@ -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)) Loading @@ -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() Loading @@ -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) Loading @@ -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() Loading @@ -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 { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt +99 −97 Original line number Diff line number Diff line Loading @@ -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, ) Loading @@ -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() Loading @@ -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 { Loading packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt +47 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 { Loading Loading @@ -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, Loading Loading @@ -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) } } packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt +20 −20 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt +177 −108 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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) Loading @@ -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)) Loading @@ -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() Loading @@ -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() Loading @@ -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)) Loading @@ -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() Loading @@ -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) Loading @@ -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() Loading @@ -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 { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt +99 −97 Original line number Diff line number Diff line Loading @@ -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, ) Loading @@ -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() Loading @@ -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 { Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt +47 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 { Loading Loading @@ -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, Loading Loading @@ -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) } }
packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt +20 −20 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt +2 −2 Original line number Diff line number Diff line Loading @@ -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