Loading core/java/android/app/notification.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,16 @@ flag { } } flag { name: "modes_ui_dnd_tile" namespace: "systemui" description: "Shows a dedicated tile for the DND mode; dependent on modes_ui" bug: "401217520" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "modes_hsum" namespace: "systemui" Loading packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt 0 → 100644 +211 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 import android.app.Flags import android.os.Handler import android.platform.test.annotations.EnableFlags import android.service.quicksettings.Tile import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.kosmos.mainCoroutineContext 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.BooleanState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.shared.QSSettingsPackageRepository import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.res.R import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.whenever @EnableFlags(Flags.FLAG_MODES_UI) @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class ModesDndTileTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testDispatcher = kosmos.testDispatcher @Mock private lateinit var qsHost: QSHost @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider @Mock private lateinit var dialogDelegate: ModesDialogDelegate @Mock private lateinit var settingsPackageRepository: QSSettingsPackageRepository private val inputHandler = FakeQSTileIntentUserInputHandler() private val zenModeRepository = kosmos.zenModeRepository private val tileDataInteractor = ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher) private val mapper = ModesDndTileMapper(context.resources, context.theme) private lateinit var userActionInteractor: ModesDndTileUserActionInteractor private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper private lateinit var underTest: ModesDndTile @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() // Allow the tile to load resources whenever(qsHost.context).thenReturn(context) whenever(qsHost.userContext).thenReturn(context) whenever(qsTileConfigProvider.getConfig(any())) .thenReturn( QSTileConfigTestBuilder.build { uiConfig = QSTileUIConfig.Resource( iconRes = R.drawable.qs_dnd_icon_off, labelRes = R.string.quick_settings_dnd_label, ) } ) userActionInteractor = ModesDndTileUserActionInteractor( kosmos.mainCoroutineContext, inputHandler, dialogDelegate, kosmos.zenModeInteractor, kosmos.modesDialogEventLogger, settingsPackageRepository, ) underTest = ModesDndTile( qsHost, uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), metricsLogger, statusBarStateController, activityStarter, qsLogger, qsTileConfigProvider, tileDataInteractor, mapper, userActionInteractor, ) underTest.initialize() underTest.setListening(Object(), true) testableLooper.processAllMessages() } @After fun tearDown() { underTest.destroy() testableLooper.processAllMessages() } @Test fun stateUpdatesOnChange() = testScope.runTest { assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE) zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) runCurrent() testableLooper.processAllMessages() assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE) } @Test fun handleUpdateState_withModel_updatesState() = testScope.runTest { val tileState = BooleanState().apply { state = Tile.STATE_INACTIVE secondaryLabel = "Old secondary label" } val model = ModesDndTileModel(isActivated = true) underTest.handleUpdateState(tileState, model) assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE) assertThat(tileState.secondaryLabel).isEqualTo("On") } @Test fun handleUpdateState_withNull_updatesState() = testScope.runTest { val tileState = BooleanState().apply { state = Tile.STATE_INACTIVE secondaryLabel = "Old secondary label" } zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) runCurrent() underTest.handleUpdateState(tileState, null) assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE) assertThat(tileState.secondaryLabel).isEqualTo("On") } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt 0 → 100644 +118 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.modes.domain.interactor import android.app.Flags import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toCollection import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @EnableFlags(Flags.FLAG_MODES_UI) @RunWith(AndroidJUnit4::class) class ModesDndTileDataInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val dispatcher = kosmos.testDispatcher private val zenModeRepository = kosmos.fakeZenModeRepository private val underTest by lazy { ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher) } @Test @EnableFlags(Flags.FLAG_MODES_UI_DND_TILE) fun availability_flagOn_isTrue() = testScope.runTest { val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) assertThat(availability).containsExactly(true) } @Test @DisableFlags(Flags.FLAG_MODES_UI_DND_TILE) fun availability_flagOff_isFalse() = testScope.runTest { val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) assertThat(availability).containsExactly(false) } @Test fun tileData_dndChanges_updateActivated() = testScope.runTest { val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model!!.isActivated).isFalse() zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) runCurrent() assertThat(model!!.isActivated).isTrue() zenModeRepository.deactivateMode(TestModeBuilder.MANUAL_DND) runCurrent() assertThat(model!!.isActivated).isFalse() } @Test fun tileData_otherModeChanges_notActivated() = testScope.runTest { val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model!!.isActivated).isFalse() zenModeRepository.addMode("Other mode") runCurrent() assertThat(model!!.isActivated).isFalse() zenModeRepository.activateMode("Other mode") runCurrent() assertThat(model!!.isActivated).isFalse() } private companion object { val TEST_USER = UserHandle.of(1)!! } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt 0 → 100644 +134 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.modes.domain.interactor import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.mainCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.qs.shared.QSSettingsPackageRepository import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat 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.kotlin.mock import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(android.app.Flags.FLAG_MODES_UI) class ModesDndTileUserActionInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val inputHandler = kosmos.qsTileIntentUserInputHandler private val mockDialogDelegate = kosmos.mockModesDialogDelegate private val zenModeRepository = kosmos.zenModeRepository private val zenModeInteractor = kosmos.zenModeInteractor private val settingsPackageRepository = mock<QSSettingsPackageRepository>() private val underTest = ModesDndTileUserActionInteractor( kosmos.mainCoroutineContext, inputHandler, mockDialogDelegate, zenModeInteractor, kosmos.modesDialogEventLogger, settingsPackageRepository, ) @Before fun setUp() { whenever(settingsPackageRepository.getSettingsPackageName()).thenReturn(SETTINGS_PACKAGE) } @Test fun handleClick_dndActive_deactivatesDnd() = testScope.runTest { val dndMode by collectLastValue(zenModeInteractor.dndMode) zenModeRepository.activateMode(MANUAL_DND) assertThat(dndMode?.isActive).isTrue() underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(true))) assertThat(dndMode?.isActive).isFalse() } @Test fun handleClick_dndInactive_activatesDnd() = testScope.runTest { val dndMode by collectLastValue(zenModeInteractor.dndMode) assertThat(dndMode?.isActive).isFalse() underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(false))) assertThat(dndMode?.isActive).isTrue() } @Test fun handleLongClick_active_opensSettings() = testScope.runTest { zenModeRepository.activateMode(MANUAL_DND) runCurrent() underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(true))) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE) assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) .isEqualTo(MANUAL_DND.id) } } @Test fun handleLongClick_inactive_opensSettings() = testScope.runTest { zenModeRepository.activateMode(MANUAL_DND) zenModeRepository.deactivateMode(MANUAL_DND) runCurrent() underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(false))) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE) assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) .isEqualTo(MANUAL_DND.id) } } companion object { private const val SETTINGS_PACKAGE = "the.settings.package" } } packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.modes.ui import android.app.Flags import android.graphics.drawable.TestStubDrawable import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(Flags.FLAG_MODES_UI) class ModesDndTileMapperTest : SysuiTestCase() { val config = QSTileConfigTestBuilder.build { uiConfig = QSTileUIConfig.Resource( iconRes = R.drawable.qs_dnd_icon_off, labelRes = R.string.quick_settings_modes_label, ) } val underTest = ModesDndTileMapper( context.orCreateTestableResources .apply { addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable()) addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable()) } .resources, context.theme, ) @Test fun map_inactiveState() { val model = ModesDndTileModel(isActivated = false) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE) assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_off) assertThat(state.secondaryLabel).isEqualTo("Off") } @Test fun map_activeState() { val model = ModesDndTileModel(isActivated = true) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_on) assertThat(state.secondaryLabel).isEqualTo("On") } } Loading
core/java/android/app/notification.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -62,6 +62,16 @@ flag { } } flag { name: "modes_ui_dnd_tile" namespace: "systemui" description: "Shows a dedicated tile for the DND mode; dependent on modes_ui" bug: "401217520" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "modes_hsum" namespace: "systemui" Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/ModesDndTileTest.kt 0 → 100644 +211 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 import android.app.Flags import android.os.Handler import android.platform.test.annotations.EnableFlags import android.service.quicksettings.Tile import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.kosmos.mainCoroutineContext 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.BooleanState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.shared.QSSettingsPackageRepository import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesDndTileUserActionInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel import com.android.systemui.qs.tiles.impl.modes.ui.ModesDndTileMapper import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.res.R import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.kotlin.whenever @EnableFlags(Flags.FLAG_MODES_UI) @SmallTest @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class ModesDndTileTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testDispatcher = kosmos.testDispatcher @Mock private lateinit var qsHost: QSHost @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var qsTileConfigProvider: QSTileConfigProvider @Mock private lateinit var dialogDelegate: ModesDialogDelegate @Mock private lateinit var settingsPackageRepository: QSSettingsPackageRepository private val inputHandler = FakeQSTileIntentUserInputHandler() private val zenModeRepository = kosmos.zenModeRepository private val tileDataInteractor = ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher) private val mapper = ModesDndTileMapper(context.resources, context.theme) private lateinit var userActionInteractor: ModesDndTileUserActionInteractor private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper private lateinit var underTest: ModesDndTile @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) secureSettings = FakeSettings() // Allow the tile to load resources whenever(qsHost.context).thenReturn(context) whenever(qsHost.userContext).thenReturn(context) whenever(qsTileConfigProvider.getConfig(any())) .thenReturn( QSTileConfigTestBuilder.build { uiConfig = QSTileUIConfig.Resource( iconRes = R.drawable.qs_dnd_icon_off, labelRes = R.string.quick_settings_dnd_label, ) } ) userActionInteractor = ModesDndTileUserActionInteractor( kosmos.mainCoroutineContext, inputHandler, dialogDelegate, kosmos.zenModeInteractor, kosmos.modesDialogEventLogger, settingsPackageRepository, ) underTest = ModesDndTile( qsHost, uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), metricsLogger, statusBarStateController, activityStarter, qsLogger, qsTileConfigProvider, tileDataInteractor, mapper, userActionInteractor, ) underTest.initialize() underTest.setListening(Object(), true) testableLooper.processAllMessages() } @After fun tearDown() { underTest.destroy() testableLooper.processAllMessages() } @Test fun stateUpdatesOnChange() = testScope.runTest { assertThat(underTest.state.state).isEqualTo(Tile.STATE_INACTIVE) zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) runCurrent() testableLooper.processAllMessages() assertThat(underTest.state.state).isEqualTo(Tile.STATE_ACTIVE) } @Test fun handleUpdateState_withModel_updatesState() = testScope.runTest { val tileState = BooleanState().apply { state = Tile.STATE_INACTIVE secondaryLabel = "Old secondary label" } val model = ModesDndTileModel(isActivated = true) underTest.handleUpdateState(tileState, model) assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE) assertThat(tileState.secondaryLabel).isEqualTo("On") } @Test fun handleUpdateState_withNull_updatesState() = testScope.runTest { val tileState = BooleanState().apply { state = Tile.STATE_INACTIVE secondaryLabel = "Old secondary label" } zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) runCurrent() underTest.handleUpdateState(tileState, null) assertThat(tileState.state).isEqualTo(Tile.STATE_ACTIVE) assertThat(tileState.secondaryLabel).isEqualTo("On") } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileDataInteractorTest.kt 0 → 100644 +118 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.modes.domain.interactor import android.app.Flags import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toCollection import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @EnableFlags(Flags.FLAG_MODES_UI) @RunWith(AndroidJUnit4::class) class ModesDndTileDataInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val dispatcher = kosmos.testDispatcher private val zenModeRepository = kosmos.fakeZenModeRepository private val underTest by lazy { ModesDndTileDataInteractor(context, kosmos.zenModeInteractor, dispatcher) } @Test @EnableFlags(Flags.FLAG_MODES_UI_DND_TILE) fun availability_flagOn_isTrue() = testScope.runTest { val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) assertThat(availability).containsExactly(true) } @Test @DisableFlags(Flags.FLAG_MODES_UI_DND_TILE) fun availability_flagOff_isFalse() = testScope.runTest { val availability = underTest.availability(TEST_USER).toCollection(mutableListOf()) assertThat(availability).containsExactly(false) } @Test fun tileData_dndChanges_updateActivated() = testScope.runTest { val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model!!.isActivated).isFalse() zenModeRepository.activateMode(TestModeBuilder.MANUAL_DND) runCurrent() assertThat(model!!.isActivated).isTrue() zenModeRepository.deactivateMode(TestModeBuilder.MANUAL_DND) runCurrent() assertThat(model!!.isActivated).isFalse() } @Test fun tileData_otherModeChanges_notActivated() = testScope.runTest { val model by collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) runCurrent() assertThat(model!!.isActivated).isFalse() zenModeRepository.addMode("Other mode") runCurrent() assertThat(model!!.isActivated).isFalse() zenModeRepository.activateMode("Other mode") runCurrent() assertThat(model!!.isActivated).isFalse() } private companion object { val TEST_USER = UserHandle.of(1)!! } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesDndTileUserActionInteractorTest.kt 0 → 100644 +134 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.modes.domain.interactor import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.mainCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.qs.shared.QSSettingsPackageRepository import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat 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.kotlin.mock import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(android.app.Flags.FLAG_MODES_UI) class ModesDndTileUserActionInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val inputHandler = kosmos.qsTileIntentUserInputHandler private val mockDialogDelegate = kosmos.mockModesDialogDelegate private val zenModeRepository = kosmos.zenModeRepository private val zenModeInteractor = kosmos.zenModeInteractor private val settingsPackageRepository = mock<QSSettingsPackageRepository>() private val underTest = ModesDndTileUserActionInteractor( kosmos.mainCoroutineContext, inputHandler, mockDialogDelegate, zenModeInteractor, kosmos.modesDialogEventLogger, settingsPackageRepository, ) @Before fun setUp() { whenever(settingsPackageRepository.getSettingsPackageName()).thenReturn(SETTINGS_PACKAGE) } @Test fun handleClick_dndActive_deactivatesDnd() = testScope.runTest { val dndMode by collectLastValue(zenModeInteractor.dndMode) zenModeRepository.activateMode(MANUAL_DND) assertThat(dndMode?.isActive).isTrue() underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(true))) assertThat(dndMode?.isActive).isFalse() } @Test fun handleClick_dndInactive_activatesDnd() = testScope.runTest { val dndMode by collectLastValue(zenModeInteractor.dndMode) assertThat(dndMode?.isActive).isFalse() underTest.handleInput(QSTileInputTestKtx.click(data = ModesDndTileModel(false))) assertThat(dndMode?.isActive).isTrue() } @Test fun handleLongClick_active_opensSettings() = testScope.runTest { zenModeRepository.activateMode(MANUAL_DND) runCurrent() underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(true))) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE) assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) .isEqualTo(MANUAL_DND.id) } } @Test fun handleLongClick_inactive_opensSettings() = testScope.runTest { zenModeRepository.activateMode(MANUAL_DND) zenModeRepository.deactivateMode(MANUAL_DND) runCurrent() underTest.handleInput(QSTileInputTestKtx.longClick(ModesDndTileModel(false))) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { assertThat(it.intent.`package`).isEqualTo(SETTINGS_PACKAGE) assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS) assertThat(it.intent.getStringExtra(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)) .isEqualTo(MANUAL_DND.id) } } companion object { private const val SETTINGS_PACKAGE = "the.settings.package" } }
packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesDndTileMapperTest.kt 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.modes.ui import android.app.Flags import android.graphics.drawable.TestStubDrawable import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesDndTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(Flags.FLAG_MODES_UI) class ModesDndTileMapperTest : SysuiTestCase() { val config = QSTileConfigTestBuilder.build { uiConfig = QSTileUIConfig.Resource( iconRes = R.drawable.qs_dnd_icon_off, labelRes = R.string.quick_settings_modes_label, ) } val underTest = ModesDndTileMapper( context.orCreateTestableResources .apply { addOverride(R.drawable.qs_dnd_icon_on, TestStubDrawable()) addOverride(R.drawable.qs_dnd_icon_off, TestStubDrawable()) } .resources, context.theme, ) @Test fun map_inactiveState() { val model = ModesDndTileModel(isActivated = false) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE) assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_off) assertThat(state.secondaryLabel).isEqualTo("Off") } @Test fun map_activeState() { val model = ModesDndTileModel(isActivated = true) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) assertThat((state.icon as Icon.Loaded).res).isEqualTo(R.drawable.qs_dnd_icon_on) assertThat(state.secondaryLabel).isEqualTo("On") } }