Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +18 −0 Original line number Diff line number Diff line Loading @@ -675,6 +675,24 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { assertThat(tiles!!.size).isEqualTo(3) } @Test fun changeInPackagesTiles_doesntTriggerUserChange_logged() = testScope.runTest(USER_INFO_0) { val specs = listOf( TileSpec.create("a"), ) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() // Settled on the same list of tiles. assertThat(underTest.currentTilesSpecs).isEqualTo(specs) installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet()) runCurrent() verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0) } private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { this.state = state this.label = label Loading packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +100 −104 Original line number Diff line number Diff line Loading @@ -46,7 +46,7 @@ import com.android.systemui.qs.toProto import com.android.systemui.retail.data.repository.RetailModeRepository import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.pairwiseBy import dagger.Lazy import java.io.PrintWriter import javax.inject.Inject Loading @@ -63,7 +63,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext Loading Loading @@ -169,16 +168,18 @@ constructor( private val userAndTiles = currentUser .flatMapLatest { userId -> tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) } val currentTiles = tileSpecRepository.tilesSpecs(userId) val installedComponents = installedTilesComponentRepository.getInstalledTilesComponents(userId) currentTiles.combine(installedComponents) { tiles, components -> UserTilesAndComponents(userId, tiles, components) } } .distinctUntilChanged() .pairwise(UserAndTiles(-1, emptyList())) .flowOn(backgroundDispatcher) private val installedPackagesWithTiles = currentUser.flatMapLatest { installedTilesComponentRepository.getInstalledTilesComponents(it) .pairwiseBy(UserTilesAndComponents(-1, emptyList(), emptySet())) { prev, new -> DataWithUserChange(data = new, userChange = prev.userId != new.userId) } .flowOn(backgroundDispatcher) private val minTiles: Int get() = Loading @@ -194,7 +195,6 @@ constructor( } } @OptIn(ExperimentalCoroutinesApi::class) private fun startTileCollection() { scope.launch { launch { Loading @@ -205,25 +205,15 @@ constructor( } launch(backgroundDispatcher) { userAndTiles .combine(installedPackagesWithTiles) { usersAndTiles, packages -> Data( usersAndTiles.previousValue, usersAndTiles.newValue, packages, ) } .collectLatest { val newTileList = it.newData.tiles val userChanged = it.oldData.userId != it.newData.userId val newUser = it.newData.userId userAndTiles.collectLatest { val newUser = it.userId val newTileList = it.tiles val components = it.installedComponents val userChanged = it.userChange // Destroy all tiles that are not in the new set specsToTiles .filter { it.key !in newTileList && it.value is TileOrNotInstalled.Tile } .filter { it.key !in newTileList && it.value is TileOrNotInstalled.Tile } .forEach { entry -> logger.logTileDestroyed( entry.key, Loading Loading @@ -256,8 +246,7 @@ constructor( specsToTiles.getValue(tileSpec), userChanged, newUser ) ?: createTile(tileSpec) ) ?: createTile(tileSpec) } else { createTile(tileSpec) } Loading @@ -274,9 +263,7 @@ constructor( val newResolvedTiles = newTileMap .filter { it.value is TileOrNotInstalled.Tile } .map { TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile) } .map { TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile) } _currentSpecsAndTiles.value = newResolvedTiles logger.logTilesNotInstalled( Loading Loading @@ -362,8 +349,7 @@ constructor( newQSTileFactory.get().createTile(spec.spec) } else { null } ?: tileFactory.createTile(spec.spec) } ?: tileFactory.createTile(spec.spec) } if (tile == null) { logger.logTileNotFoundInFactory(spec) Loading Loading @@ -436,15 +422,25 @@ constructor( @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled } } private data class UserAndTiles( private data class UserTilesAndComponents( val userId: Int, val tiles: List<TileSpec>, val installedComponents: Set<ComponentName> ) private data class Data( val oldData: UserAndTiles, val newData: UserAndTiles, private data class DataWithUserChange( val userId: Int, val tiles: List<TileSpec>, val installedComponents: Set<ComponentName>, val userChange: Boolean, ) private fun DataWithUserChange(data: UserTilesAndComponents, userChange: Boolean) = DataWithUserChange( data.userId, data.tiles, data.installedComponents, userChange, ) } packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +2 −1 Original line number Diff line number Diff line Loading @@ -106,7 +106,8 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements @Override @MainThread public void onManagedProfileRemoved() { mHost.removeTile(getTileSpec()); // No OP as this may race with the user change in CurrentTilesInteractor. // If the tile needs to be removed, AutoAdd (or AutoTileManager) will take care of that. } @Override Loading packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt +6 −1 Original line number Diff line number Diff line Loading @@ -22,12 +22,15 @@ import androidx.annotation.StringRes import com.android.internal.logging.InstanceId import com.android.systemui.qs.pipeline.shared.TileSpec data class QSTileConfig( data class QSTileConfig @JvmOverloads constructor( val tileSpec: TileSpec, val uiConfig: QSTileUIConfig, val instanceId: InstanceId, val metricsSpec: String = tileSpec.spec, val policy: QSTilePolicy = QSTilePolicy.NoRestrictions, val autoRemoveOnUnavailable: Boolean = true, ) /** Loading @@ -38,6 +41,7 @@ sealed interface QSTileUIConfig { val iconRes: Int @DrawableRes get val labelRes: Int @StringRes get Loading @@ -48,6 +52,7 @@ sealed interface QSTileUIConfig { data object Empty : QSTileUIConfig { override val iconRes: Int get() = Resources.ID_NULL override val labelRes: Int get() = Resources.ID_NULL } Loading packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +1 −1 Original line number Diff line number Diff line Loading @@ -70,7 +70,7 @@ constructor( applicationScope.launch { launch { qsTileViewModel.isAvailable.collectIndexed { index, isAvailable -> if (!isAvailable) { if (!isAvailable && qsTileViewModel.config.autoRemoveOnUnavailable) { qsHost.removeTile(tileSpec) } // qsTileViewModel.isAvailable flow often starts with isAvailable == true. Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +18 −0 Original line number Diff line number Diff line Loading @@ -675,6 +675,24 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { assertThat(tiles!!.size).isEqualTo(3) } @Test fun changeInPackagesTiles_doesntTriggerUserChange_logged() = testScope.runTest(USER_INFO_0) { val specs = listOf( TileSpec.create("a"), ) tileSpecRepository.setTiles(USER_INFO_0.id, specs) runCurrent() // Settled on the same list of tiles. assertThat(underTest.currentTilesSpecs).isEqualTo(specs) installedTilesPackageRepository.setInstalledPackagesForUser(USER_INFO_0.id, emptySet()) runCurrent() verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0) } private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) { this.state = state this.label = label Loading
packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +100 −104 Original line number Diff line number Diff line Loading @@ -46,7 +46,7 @@ import com.android.systemui.qs.toProto import com.android.systemui.retail.data.repository.RetailModeRepository import com.android.systemui.settings.UserTracker import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.pairwiseBy import dagger.Lazy import java.io.PrintWriter import javax.inject.Inject Loading @@ -63,7 +63,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext Loading Loading @@ -169,16 +168,18 @@ constructor( private val userAndTiles = currentUser .flatMapLatest { userId -> tileSpecRepository.tilesSpecs(userId).map { UserAndTiles(userId, it) } val currentTiles = tileSpecRepository.tilesSpecs(userId) val installedComponents = installedTilesComponentRepository.getInstalledTilesComponents(userId) currentTiles.combine(installedComponents) { tiles, components -> UserTilesAndComponents(userId, tiles, components) } } .distinctUntilChanged() .pairwise(UserAndTiles(-1, emptyList())) .flowOn(backgroundDispatcher) private val installedPackagesWithTiles = currentUser.flatMapLatest { installedTilesComponentRepository.getInstalledTilesComponents(it) .pairwiseBy(UserTilesAndComponents(-1, emptyList(), emptySet())) { prev, new -> DataWithUserChange(data = new, userChange = prev.userId != new.userId) } .flowOn(backgroundDispatcher) private val minTiles: Int get() = Loading @@ -194,7 +195,6 @@ constructor( } } @OptIn(ExperimentalCoroutinesApi::class) private fun startTileCollection() { scope.launch { launch { Loading @@ -205,25 +205,15 @@ constructor( } launch(backgroundDispatcher) { userAndTiles .combine(installedPackagesWithTiles) { usersAndTiles, packages -> Data( usersAndTiles.previousValue, usersAndTiles.newValue, packages, ) } .collectLatest { val newTileList = it.newData.tiles val userChanged = it.oldData.userId != it.newData.userId val newUser = it.newData.userId userAndTiles.collectLatest { val newUser = it.userId val newTileList = it.tiles val components = it.installedComponents val userChanged = it.userChange // Destroy all tiles that are not in the new set specsToTiles .filter { it.key !in newTileList && it.value is TileOrNotInstalled.Tile } .filter { it.key !in newTileList && it.value is TileOrNotInstalled.Tile } .forEach { entry -> logger.logTileDestroyed( entry.key, Loading Loading @@ -256,8 +246,7 @@ constructor( specsToTiles.getValue(tileSpec), userChanged, newUser ) ?: createTile(tileSpec) ) ?: createTile(tileSpec) } else { createTile(tileSpec) } Loading @@ -274,9 +263,7 @@ constructor( val newResolvedTiles = newTileMap .filter { it.value is TileOrNotInstalled.Tile } .map { TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile) } .map { TileModel(it.key, (it.value as TileOrNotInstalled.Tile).tile) } _currentSpecsAndTiles.value = newResolvedTiles logger.logTilesNotInstalled( Loading Loading @@ -362,8 +349,7 @@ constructor( newQSTileFactory.get().createTile(spec.spec) } else { null } ?: tileFactory.createTile(spec.spec) } ?: tileFactory.createTile(spec.spec) } if (tile == null) { logger.logTileNotFoundInFactory(spec) Loading Loading @@ -436,15 +422,25 @@ constructor( @JvmInline value class Tile(val tile: QSTile) : TileOrNotInstalled } } private data class UserAndTiles( private data class UserTilesAndComponents( val userId: Int, val tiles: List<TileSpec>, val installedComponents: Set<ComponentName> ) private data class Data( val oldData: UserAndTiles, val newData: UserAndTiles, private data class DataWithUserChange( val userId: Int, val tiles: List<TileSpec>, val installedComponents: Set<ComponentName>, val userChange: Boolean, ) private fun DataWithUserChange(data: UserTilesAndComponents, userChange: Boolean) = DataWithUserChange( data.userId, data.tiles, data.installedComponents, userChange, ) }
packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +2 −1 Original line number Diff line number Diff line Loading @@ -106,7 +106,8 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements @Override @MainThread public void onManagedProfileRemoved() { mHost.removeTile(getTileSpec()); // No OP as this may race with the user change in CurrentTilesInteractor. // If the tile needs to be removed, AutoAdd (or AutoTileManager) will take care of that. } @Override Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt +6 −1 Original line number Diff line number Diff line Loading @@ -22,12 +22,15 @@ import androidx.annotation.StringRes import com.android.internal.logging.InstanceId import com.android.systemui.qs.pipeline.shared.TileSpec data class QSTileConfig( data class QSTileConfig @JvmOverloads constructor( val tileSpec: TileSpec, val uiConfig: QSTileUIConfig, val instanceId: InstanceId, val metricsSpec: String = tileSpec.spec, val policy: QSTilePolicy = QSTilePolicy.NoRestrictions, val autoRemoveOnUnavailable: Boolean = true, ) /** Loading @@ -38,6 +41,7 @@ sealed interface QSTileUIConfig { val iconRes: Int @DrawableRes get val labelRes: Int @StringRes get Loading @@ -48,6 +52,7 @@ sealed interface QSTileUIConfig { data object Empty : QSTileUIConfig { override val iconRes: Int get() = Resources.ID_NULL override val labelRes: Int get() = Resources.ID_NULL } Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +1 −1 Original line number Diff line number Diff line Loading @@ -70,7 +70,7 @@ constructor( applicationScope.launch { launch { qsTileViewModel.isAvailable.collectIndexed { index, isAvailable -> if (!isAvailable) { if (!isAvailable && qsTileViewModel.config.autoRemoveOnUnavailable) { qsHost.removeTile(tileSpec) } // qsTileViewModel.isAvailable flow often starts with isAvailable == true. Loading