Skip to content
Snippets Groups Projects
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
No related branches found
No related tags found
No related merge requests found
Showing
with 467 additions and 230 deletions
......@@ -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,194 +47,260 @@ 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 =
CustomTileRepositoryImpl(
TileSpec.create(TEST_COMPONENT),
persister,
testScope.testScheduler,
)
with(kosmos) {
CustomTileRepositoryImpl(
tileSpec,
customTileStatePersister,
packageManagerAdapterFacade.packageManagerAdapter,
testScope.testScheduler,
)
}
@Test
fun persistableTileIsRestoredForUser() =
testScope.runTest {
persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
persister.persistState(TEST_TILE_KEY_2, TEST_TILE_2)
with(kosmos) {
testScope.runTest {
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()
underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
runCurrent()
assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
}
}
@Test
fun notPersistableTileIsNotRestored() =
testScope.runTest {
persister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
val tiles = collectValues(underTest.getTiles(TEST_USER_1))
with(kosmos) {
testScope.runTest {
customTileStatePersister.persistState(TEST_TILE_KEY_1, TEST_TILE_1)
val tiles = collectValues(underTest.getTiles(TEST_USER_1))
underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
runCurrent()
underTest.restoreForTheUserIfNeeded(TEST_USER_1, false)
runCurrent()
assertThat(tiles()).isEmpty()
assertThat(tiles()).isEmpty()
}
}
@Test
fun emptyPersistedStateIsHandled() =
testScope.runTest {
val tiles = collectValues(underTest.getTiles(TEST_USER_1))
with(kosmos) {
testScope.runTest {
val tiles = collectValues(underTest.getTiles(TEST_USER_1))
underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
runCurrent()
underTest.restoreForTheUserIfNeeded(TEST_USER_1, true)
runCurrent()
assertThat(tiles()).isEmpty()
assertThat(tiles()).isEmpty()
}
}
@Test
fun updatingWithPersistableTilePersists() =
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
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() =
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, false)
runCurrent()
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() =
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
with(kosmos) {
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
}
}
@Test
fun updatingPeristableWithDefaultsPersists() =
testScope.runTest {
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
runCurrent()
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() =
testScope.runTest {
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, false)
runCurrent()
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() =
testScope.runTest {
underTest.updateWithDefaults(TEST_USER_1, CustomTileDefaults.Error, true)
runCurrent()
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() =
testScope.runTest {
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
runCurrent()
with(kosmos) {
testScope.runTest {
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
runCurrent()
assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTiles(TEST_USER_1).first()).isEqualTo(TEST_TILE_1)
assertThat(underTest.getTile(TEST_USER_1)).isEqualTo(TEST_TILE_1)
}
}
@Test
fun getTileForAnotherUserReturnsNull() =
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
with(kosmos) {
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
assertThat(underTest.getTile(TEST_USER_2)).isNull()
assertThat(underTest.getTile(TEST_USER_2)).isNull()
}
}
@Test
fun getTilesForAnotherUserEmpty() =
testScope.runTest {
val tiles = collectValues(underTest.getTiles(TEST_USER_2))
with(kosmos) {
testScope.runTest {
val tiles = collectValues(underTest.getTiles(TEST_USER_2))
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
assertThat(tiles()).isEmpty()
assertThat(tiles()).isEmpty()
}
}
@Test
fun updatingWithTileForTheSameUserAddsData() =
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
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)
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
)
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() =
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
val tiles = collectValues(underTest.getTiles(TEST_USER_2))
underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
runCurrent()
assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
assertThat(tiles()).hasSize(1)
assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
with(kosmos) {
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, TEST_TILE_1, true)
runCurrent()
val tiles = collectValues(underTest.getTiles(TEST_USER_2))
underTest.updateWithTile(TEST_USER_2, TEST_TILE_2, true)
runCurrent()
assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
assertThat(tiles()).hasSize(1)
assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
}
}
@Test
fun updatingWithDefaultsForTheSameUserAddsData() =
testScope.runTest {
underTest.updateWithTile(TEST_USER_1, Tile().apply { subtitle = "test_subtitle" }, true)
runCurrent()
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, 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)
with(kosmos) {
testScope.runTest {
underTest.updateWithTile(
TEST_USER_1,
Tile().apply { subtitle = "test_subtitle" },
true
)
runCurrent()
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, 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 updatingWithDefaultsForAnotherUserOverridesTile() =
testScope.runTest {
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
runCurrent()
with(kosmos) {
testScope.runTest {
underTest.updateWithDefaults(TEST_USER_1, TEST_DEFAULTS_1, true)
runCurrent()
val tiles = collectValues(underTest.getTiles(TEST_USER_2))
underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
runCurrent()
assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
assertThat(tiles()).hasSize(1)
assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
}
}
val tiles = collectValues(underTest.getTiles(TEST_USER_2))
underTest.updateWithDefaults(TEST_USER_2, TEST_DEFAULTS_2, true)
runCurrent()
@Test
fun isActiveFollowsPackageManagerAdapter() =
with(kosmos) {
testScope.runTest {
packageManagerAdapterFacade.setIsActive(false)
assertThat(underTest.isTileActive()).isFalse()
packageManagerAdapterFacade.setIsActive(true)
assertThat(underTest.isTileActive()).isTrue()
}
}
assertThat(underTest.getTile(TEST_USER_2)).isEqualTo(TEST_TILE_2)
assertThat(tiles()).hasSize(1)
assertThat(tiles().last()).isEqualTo(TEST_TILE_2)
@Test
fun isToggleableFollowsPackageManagerAdapter() =
with(kosmos) {
testScope.runTest {
packageManagerAdapterFacade.setIsToggleable(false)
assertThat(underTest.isTileToggleable()).isFalse()
packageManagerAdapterFacade.setIsToggleable(true)
assertThat(underTest.isTileToggleable()).isTrue()
}
}
private companion object {
......
......@@ -25,157 +25,159 @@ 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,
)
}
}
@Test
fun activeTileIsAvailableAfterRestored() =
testScope.runTest {
whenever(tileServiceManager.isActiveTile).thenReturn(true)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
underTest.init()
assertThat(underTest.tile).isEqualTo(TEST_TILE)
assertThat(underTest.tiles.first()).isEqualTo(TEST_TILE)
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(true)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
underTest.initForUser(TEST_USER)
assertThat(underTest.getTile(TEST_USER)).isEqualTo(TEST_TILE)
assertThat(underTest.getTiles(TEST_USER).first()).isEqualTo(TEST_TILE)
}
}
@Test
fun notActiveTileIsAvailableAfterUpdated() =
testScope.runTest {
whenever(tileServiceManager.isActiveTile).thenReturn(false)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
val tiles = collectValues(underTest.tiles)
val initJob = launch { underTest.init() }
underTest.updateTile(TEST_TILE)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
assertThat(tiles().last()).isEqualTo(TEST_TILE)
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
val tiles = collectValues(underTest.getTiles(TEST_USER))
val initJob = launch { underTest.initForUser(TEST_USER) }
underTest.updateTile(TEST_TILE)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
assertThat(tiles().last()).isEqualTo(TEST_TILE)
}
}
@Test
fun notActiveTileIsAvailableAfterDefaultsUpdated() =
testScope.runTest {
whenever(tileServiceManager.isActiveTile).thenReturn(false)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
val tiles = collectValues(underTest.tiles)
val initJob = launch { underTest.init() }
defaultsRepository.putDefaults(TEST_USER, TEST_COMPONENT, TEST_DEFAULTS)
defaultsRepository.requestNewDefaults(TEST_USER, TEST_COMPONENT)
runCurrent()
initJob.join()
assertThat(tiles()).hasSize(1)
assertThat(tiles().last()).isEqualTo(TEST_TILE)
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
val tiles = collectValues(underTest.getTiles(TEST_USER))
val initJob = launch { underTest.initForUser(TEST_USER) }
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() =
testScope.runTest {
whenever(tileServiceManager.isActiveTile).thenReturn(true)
val tiles = collectValues(underTest.tiles)
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(true)
val tiles = collectValues(underTest.getTiles(TEST_USER))
val initJob = backgroundScope.launch { underTest.init() }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
assertThat(initJob.isActive).isTrue()
assertThat(tiles()).isEmpty()
// Is still suspended
assertThat(initJob.isActive).isTrue()
assertThat(tiles()).isEmpty()
}
}
@Test
fun initSuspendedForNotActiveTileWithoutUpdates() =
testScope.runTest {
whenever(tileServiceManager.isActiveTile).thenReturn(false)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
val tiles = collectValues(underTest.tiles)
val initJob = backgroundScope.launch { underTest.init() }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
with(kosmos) {
testScope.runTest {
customTileRepository.setTileActive(false)
customTileStatePersister.persistState(
TileServiceKey(TEST_COMPONENT, TEST_USER.identifier),
TEST_TILE,
)
val tiles = collectValues(underTest.getTiles(TEST_USER))
val initJob = backgroundScope.launch { underTest.initForUser(TEST_USER) }
advanceTimeBy(1 * DateUtils.DAY_IN_MILLIS)
// Is still suspended
assertThat(initJob.isActive).isTrue()
assertThat(tiles()).isEmpty()
}
}
// 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 {
......
......@@ -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)
}
}
......@@ -19,10 +19,9 @@ package com.android.systemui.qs.tiles.impl.custom.domain.interactor
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.qs.external.TileServiceManager
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundScope
import com.android.systemui.qs.tiles.impl.di.QSTileScope
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
......@@ -35,15 +34,13 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
/** Manages updates of the [Tile] assigned for the current custom tile. */
@CustomTileBoundScope
@QSTileScope
class CustomTileInteractor
@Inject
constructor(
private val user: UserHandle,
private val defaultsRepository: CustomTileDefaultsRepository,
private val customTileRepository: CustomTileRepository,
private val tileServiceManager: TileServiceManager,
@CustomTileBoundScope private val boundScope: CoroutineScope,
@QSTileScope private val tileScope: CoroutineScope,
@Background private val backgroundContext: CoroutineContext,
) {
......@@ -51,8 +48,7 @@ constructor(
MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
/** [Tile] updates. [updateTile] to emit a new one. */
val tiles: Flow<Tile>
get() = customTileRepository.getTiles(user)
fun getTiles(user: UserHandle): Flow<Tile> = customTileRepository.getTiles(user)
/**
* Current [Tile]
......@@ -61,10 +57,14 @@ constructor(
* the tile hasn't been updated for the current user. Can happen when this is accessed before
* [init] returns.
*/
val tile: Tile
get() =
customTileRepository.getTile(user)
?: throw IllegalStateException("Attempt to get a tile for a wrong user")
fun getTile(user: UserHandle): Tile =
customTileRepository.getTile(user)
?: throw IllegalStateException("Attempt to get a tile for a wrong user")
/**
* True if the tile is toggleable like a switch and false if it operates as a clickable button.
*/
suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
/**
* Initializes the repository for the current user. Suspends until it's safe to call [tile]
......@@ -73,36 +73,36 @@ constructor(
* - receive tile update in [updateTile];
* - restoration happened for a persisted tile.
*/
suspend fun init() {
launchUpdates()
customTileRepository.restoreForTheUserIfNeeded(user, tileServiceManager.isActiveTile)
suspend fun initForUser(user: UserHandle) {
launchUpdates(user)
customTileRepository.restoreForTheUserIfNeeded(user, customTileRepository.isTileActive())
// Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
// tile update.
customTileRepository.getTiles(user).firstOrNull()
}
private fun launchUpdates() {
private fun launchUpdates(user: UserHandle) {
tileUpdates
.onEach {
customTileRepository.updateWithTile(
user,
it,
tileServiceManager.isActiveTile,
customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
.launchIn(boundScope)
.launchIn(tileScope)
defaultsRepository
.defaults(user)
.onEach {
customTileRepository.updateWithDefaults(
user,
it,
tileServiceManager.isActiveTile,
customTileRepository.isTileActive(),
)
}
.flowOn(backgroundContext)
.launchIn(boundScope)
.launchIn(tileScope)
}
/** Updates current [Tile]. Emits a new event in [tiles]. */
......
......@@ -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.
......
/*
* Copyright (C) 2023 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.tiles.impl.custom
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.external.FakeCustomTileStatePersister
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileDefaultsRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTilePackageUpdatesRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakeCustomTileRepository
import com.android.systemui.qs.tiles.impl.custom.data.repository.FakePackageManagerAdapterFacade
var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture()
val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by
Kosmos.Fixture { FakeCustomTileStatePersister() }
val Kosmos.customTileRepository: FakeCustomTileRepository by
Kosmos.Fixture {
FakeCustomTileRepository(
customTileStatePersister,
packageManagerAdapterFacade,
testScope.testScheduler,
)
}
val Kosmos.customTileDefaultsRepository: FakeCustomTileDefaultsRepository by
Kosmos.Fixture { FakeCustomTileDefaultsRepository() }
val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepository by
Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec) }
......@@ -19,21 +19,21 @@ package com.android.systemui.qs.tiles.impl.custom.data.repository
import android.os.UserHandle
import android.service.quicksettings.Tile
import com.android.systemui.qs.external.FakeCustomTileStatePersister
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.flow.Flow
class FakeCustomTileRepository(
tileSpec: TileSpec.CustomTileSpec,
customTileStatePersister: FakeCustomTileStatePersister,
private val packageManagerAdapterFacade: FakePackageManagerAdapterFacade,
testBackgroundContext: CoroutineContext,
) : CustomTileRepository {
private val realDelegate: CustomTileRepository =
CustomTileRepositoryImpl(
tileSpec,
packageManagerAdapterFacade.tileSpec,
customTileStatePersister,
packageManagerAdapterFacade.packageManagerAdapter,
testBackgroundContext,
)
......@@ -44,6 +44,10 @@ class FakeCustomTileRepository(
override fun getTile(user: UserHandle): Tile? = realDelegate.getTile(user)
override suspend fun isTileActive(): Boolean = realDelegate.isTileActive()
override suspend fun isTileToggleable(): Boolean = realDelegate.isTileToggleable()
override suspend fun updateWithTile(
user: UserHandle,
newTile: Tile,
......@@ -55,4 +59,8 @@ class FakeCustomTileRepository(
defaults: CustomTileDefaults,
isPersistable: Boolean,
) = realDelegate.updateWithDefaults(user, defaults, isPersistable)
fun setTileActive(isActive: Boolean) = packageManagerAdapterFacade.setIsActive(isActive)
fun setTileToggleable(isActive: Boolean) = packageManagerAdapterFacade.setIsToggleable(isActive)
}
/*
* Copyright (C) 2023 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.tiles.impl.custom.data.repository
import android.content.pm.ServiceInfo
import android.os.Bundle
import com.android.systemui.qs.external.PackageManagerAdapter
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
class FakePackageManagerAdapterFacade(
val tileSpec: TileSpec.CustomTileSpec,
val packageManagerAdapter: PackageManagerAdapter = mock {},
) {
private var isToggleable: Boolean = false
private var isActive: Boolean = false
init {
whenever(packageManagerAdapter.getServiceInfo(eq(tileSpec.componentName), any()))
.thenAnswer {
ServiceInfo().apply {
metaData =
Bundle().apply {
putBoolean(
android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
isToggleable
)
putBoolean(
android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
isActive
)
}
}
}
}
fun setIsActive(isActive: Boolean) {
this.isActive = isActive
}
fun setIsToggleable(isToggleable: Boolean) {
this.isToggleable = isToggleable
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment