diff options
9 files changed, 183 insertions, 46 deletions
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 4371f05ead9f..043219a65da4 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 @@ class FakeZenModeRepository : ZenModeRepository { 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 0ff7f84a08b9..7fdbcdae2276 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 @@ interface ZenModeRepository { /** 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 @@ class ZenModeRepositoryImpl( } } + /** + * 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 67c73b1c8f5a..c136644e0959 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 @@ class ZenModeRepositoryTest { } } + @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 a18f450cdc9e..91d8e2a75ef0 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 @@ class ModesTileDataInteractorTest : SysuiTestCase() { 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 639d34d5e74d..fb32855ee2b7 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 @@ class ZenModeInteractorTest : SysuiTestCase() { } @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 313cb30d84ff..7d23fbdf2ece 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.Handler 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 @@ constructor( activityStarter: ActivityStarter, qsLogger: QSLogger, qsTileConfigProvider: QSTileConfigProvider, - dataInteractor: ModesTileDataInteractor, + private val dataInteractor: ModesTileDataInteractor, private val tileMapper: ModesTileMapper, private val userActionInteractor: ModesTileUserActionInteractor, ) : @@ -110,19 +111,21 @@ constructor( 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 6173091b3b99..c2d112edd65d 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.dagger.qualifiers.Background 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 @@ constructor( */ 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 93c631f65df7..dbeaa59cd219 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 @@ constructor( /** 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 d2dcf4d38d0c..848c8db4b99d 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 androidx.test.filters.SmallTest 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.logging.QSLogger 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 @@ class ModesTileTest : SysuiTestCase() { 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") + } } |