Update ModesTile state even when arg == null
This happens on user change. Ignoring it means the tile still has the old user's data on the first shade expansion.
Fixes: 361770993
Test: atest ModesTileTest ZenModeInteractorTest ZenModeRepositoryTest ModesTileDataInteractorTest
Flag: android.app.modes_ui
Change-Id: Iaef362fe6417e1ff3c872069b3cf6d1b2264cecd
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 4371f05..043219a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -41,6 +41,8 @@
override val modes: Flow<List<ZenMode>>
get() = mutableModesFlow.asStateFlow()
+ override fun getModes(): List<ZenMode> = mutableModesFlow.value
+
private val activeModesDurations = mutableMapOf<String, Duration?>()
init {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index 0ff7f84..7fdbcda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -59,6 +59,8 @@
/** A list of all existing priority modes. */
val modes: Flow<List<ZenMode>>
+ fun getModes(): List<ZenMode>
+
fun activateMode(zenMode: ZenMode, duration: Duration? = null)
fun deactivateMode(zenMode: ZenMode)
@@ -184,6 +186,15 @@
}
}
+ /**
+ * Gets the current list of [ZenMode] instances according to the backend.
+ *
+ * This is necessary, and cannot be supplanted by making [modes] a StateFlow, because it will be
+ * called whenever we know or suspect that [modes] may not have caught up to the latest data
+ * (such as right after a user switch).
+ */
+ override fun getModes(): List<ZenMode> = backend.modes
+
override fun activateMode(zenMode: ZenMode, duration: Duration?) {
backend.activateMode(zenMode, duration)
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index 67c73b1..c136644 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -199,6 +199,15 @@
}
}
+ @EnableFlags(android.app.Flags.FLAG_MODES_UI)
+ @Test
+ fun getModes_returnsModes() {
+ val modesList = listOf(TestModeBuilder().setId("One").build())
+ `when`(zenModesBackend.modes).thenReturn(modesList)
+
+ assertThat(underTest.getModes()).isEqualTo(modesList)
+ }
+
private fun triggerIntent(action: String, extras: Map<String, Parcelable>? = null) {
verify(context).registerReceiver(receiverCaptor.capture(), any(), any(), any())
val intent = Intent(action)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
index a18f450..91d8e2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -206,6 +206,39 @@
assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID)
}
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ @Test
+ fun getCurrentTileModel_returnsActiveModes() = runTest {
+ var tileData = underTest.getCurrentTileModel()
+ assertThat(tileData.isActivated).isFalse()
+ assertThat(tileData.activeModes).isEmpty()
+
+ // Add active mode
+ zenModeRepository.addMode(id = "One", active = true)
+ tileData = underTest.getCurrentTileModel()
+ assertThat(tileData.isActivated).isTrue()
+ assertThat(tileData.activeModes).containsExactly("Mode One")
+
+ // Add an inactive mode: state hasn't changed
+ zenModeRepository.addMode(id = "Two", active = false)
+ tileData = underTest.getCurrentTileModel()
+ assertThat(tileData.isActivated).isTrue()
+ assertThat(tileData.activeModes).containsExactly("Mode One")
+
+ // Add another active mode
+ zenModeRepository.addMode(id = "Three", active = true)
+ tileData = underTest.getCurrentTileModel()
+ assertThat(tileData.isActivated).isTrue()
+ assertThat(tileData.activeModes).containsExactly("Mode One", "Mode Three").inOrder()
+
+ // Remove a mode and deactivate the other
+ zenModeRepository.removeMode("One")
+ zenModeRepository.deactivateMode("Three")
+ tileData = underTest.getCurrentTileModel()
+ assertThat(tileData.isActivated).isFalse()
+ assertThat(tileData.activeModes).isEmpty()
+ }
+
private companion object {
val TEST_USER = UserHandle.of(1)!!
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 639d34d..fb32855 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -257,6 +257,36 @@
}
@Test
+ fun getActiveModes_computesMainActiveMode() = runTest {
+ zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+ zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+ var activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).hasSize(0)
+ assertThat(activeModes.mainMode).isNull()
+
+ zenModeRepository.activateMode("Other")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).containsExactly("Mode Other")
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
+
+ zenModeRepository.activateMode("Bedtime")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).containsExactly("Mode Bedtime", "Mode Other").inOrder()
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Other")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Bedtime")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).hasSize(0)
+ assertThat(activeModes.mainMode).isNull()
+ }
+
+ @Test
fun mainActiveMode_flows() =
testScope.runTest {
val mainActiveMode by collectLastValue(underTest.mainActiveMode)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 313cb30..7d23fbd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -22,6 +22,7 @@
import android.os.Looper
import android.service.quicksettings.Tile
import androidx.annotation.DrawableRes
+import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
@@ -63,7 +64,7 @@
activityStarter: ActivityStarter,
qsLogger: QSLogger,
qsTileConfigProvider: QSTileConfigProvider,
- dataInteractor: ModesTileDataInteractor,
+ private val dataInteractor: ModesTileDataInteractor,
private val tileMapper: ModesTileMapper,
private val userActionInteractor: ModesTileUserActionInteractor,
) :
@@ -110,19 +111,21 @@
override fun getLongClickIntent(): Intent = userActionInteractor.longClickIntent
- override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
- if (arg is ModesTileModel) {
- tileState = tileMapper.map(config, arg)
+ @VisibleForTesting
+ public override fun handleUpdateState(state: QSTile.State?, arg: Any?) {
+ // This runBlocking() will block @Background. Due to caches, it's expected to be fast.
+ val model =
+ if (arg is ModesTileModel) arg else runBlocking { dataInteractor.getCurrentTileModel() }
- state?.apply {
- this.state = tileState.activationState.legacyState
- val tileStateIcon = tileState.icon()
- icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
- label = tileLabel
- secondaryLabel = tileState.secondaryLabel
- contentDescription = tileState.contentDescription
- expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
- }
+ tileState = tileMapper.map(config, model)
+ state?.apply {
+ this.state = tileState.activationState.legacyState
+ val tileStateIcon = tileState.icon()
+ icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+ label = tileLabel
+ secondaryLabel = tileState.secondaryLabel
+ contentDescription = tileState.contentDescription
+ expandedAccessibilityClassName = tileState.expandedAccessibilityClassName
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
index 6173091..c2d112e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -25,8 +25,8 @@
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
-import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -54,31 +54,35 @@
*/
fun tileData() =
zenModeInteractor.activeModes
- .map { activeModes ->
- val modesIconResId = com.android.internal.R.drawable.ic_zen_priority_modes
-
- if (usesModeIcons()) {
- val mainModeDrawable = activeModes.mainMode?.icon?.drawable
- val iconResId = if (mainModeDrawable == null) modesIconResId else null
-
- ModesTileModel(
- isActivated = activeModes.isAnyActive(),
- icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(),
- iconResId = iconResId,
- activeModes = activeModes.modeNames
- )
- } else {
- ModesTileModel(
- isActivated = activeModes.isAnyActive(),
- icon = context.getDrawable(modesIconResId)!!.asIcon(),
- iconResId = modesIconResId,
- activeModes = activeModes.modeNames
- )
- }
- }
+ .map { activeModes -> buildTileData(activeModes) }
.flowOn(bgDispatcher)
.distinctUntilChanged()
+ suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes())
+
+ private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel {
+ val modesIconResId = com.android.internal.R.drawable.ic_zen_priority_modes
+
+ if (usesModeIcons()) {
+ val mainModeDrawable = activeModes.mainMode?.icon?.drawable
+ val iconResId = if (mainModeDrawable == null) modesIconResId else null
+
+ return ModesTileModel(
+ isActivated = activeModes.isAnyActive(),
+ icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(),
+ iconResId = iconResId,
+ activeModes = activeModes.modeNames
+ )
+ } else {
+ return ModesTileModel(
+ isActivated = activeModes.isAnyActive(),
+ icon = context.getDrawable(modesIconResId)!!.asIcon(),
+ iconResId = modesIconResId,
+ activeModes = activeModes.modeNames
+ )
+ }
+ }
+
override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index 93c631f..dbeaa59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -83,19 +83,21 @@
/** Flow returning the currently active mode(s), if any. */
val activeModes: Flow<ActiveZenModes> =
modes
- .map { modes ->
- val activeModesList =
- modes
- .filter { mode -> mode.isActive }
- .sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
- val mainActiveMode =
- activeModesList.firstOrNull()?.let { ZenModeInfo(it.name, getModeIcon(it)) }
-
- ActiveZenModes(activeModesList.map { m -> m.name }, mainActiveMode)
- }
+ .map { modes -> buildActiveZenModes(modes) }
.flowOn(bgDispatcher)
.distinctUntilChanged()
+ suspend fun getActiveModes() = buildActiveZenModes(zenModeRepository.getModes())
+
+ private suspend fun buildActiveZenModes(modes: List<ZenMode>): ActiveZenModes {
+ val activeModesList =
+ modes.filter { mode -> mode.isActive }.sortedWith(ZenMode.PRIORITIZING_COMPARATOR)
+ val mainActiveMode =
+ activeModesList.firstOrNull()?.let { ZenModeInfo(it.name, getModeIcon(it)) }
+
+ return ActiveZenModes(activeModesList.map { m -> m.name }, mainActiveMode)
+ }
+
val mainActiveMode: Flow<ZenModeInfo?> =
activeModes.map { a -> a.mainMode }.distinctUntilChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
index d2dcf4d..848c8db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles
+import android.graphics.drawable.TestStubDrawable
import android.os.Handler
import android.platform.test.annotations.EnableFlags
import android.service.quicksettings.Tile
@@ -26,9 +27,11 @@
import com.android.internal.logging.MetricsLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.common.shared.model.asIcon
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
@@ -36,6 +39,7 @@
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor
import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder
@@ -168,4 +172,43 @@
assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE)
}
+
+ @Test
+ fun handleUpdateState_withTileModel_updatesState() =
+ testScope.runTest {
+ val tileState =
+ QSTile.State().apply {
+ state = Tile.STATE_INACTIVE
+ secondaryLabel = "Old secondary label"
+ }
+ val model =
+ ModesTileModel(
+ isActivated = true,
+ activeModes = listOf("One", "Two"),
+ icon = TestStubDrawable().asIcon()
+ )
+
+ underTest.handleUpdateState(tileState, model)
+
+ assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(tileState.secondaryLabel).isEqualTo("2 modes are active")
+ }
+
+ @Test
+ fun handleUpdateState_withNull_updatesState() =
+ testScope.runTest {
+ val tileState =
+ QSTile.State().apply {
+ state = Tile.STATE_INACTIVE
+ secondaryLabel = "Old secondary label"
+ }
+ zenModeRepository.addMode("One", active = true)
+ zenModeRepository.addMode("Two", active = true)
+ runCurrent()
+
+ underTest.handleUpdateState(tileState, null)
+
+ assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE)
+ assertThat(tileState.secondaryLabel).isEqualTo("2 modes are active")
+ }
}