diff options
8 files changed, 200 insertions, 21 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java index 712ddc8aea4b..5eeb49a0b398 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java @@ -167,6 +167,13 @@ public class TestModeBuilder { return this; } + public TestModeBuilder setVisualEffect(int effect, boolean allowed) { + ZenPolicy newPolicy = new ZenPolicy.Builder(mRule.getZenPolicy()) + .showVisualEffect(effect, allowed).build(); + setZenPolicy(newPolicy); + return this; + } + public TestModeBuilder setEnabled(boolean enabled) { return setEnabled(enabled, /* byUser= */ false); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt index 925cf15feabc..f9b77697b767 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt @@ -16,17 +16,20 @@ package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel +import android.app.Flags import android.app.NotificationManager.Policy +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings +import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST import androidx.test.filters.SmallTest import com.android.settingslib.notification.data.repository.updateNotificationPolicy +import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.andSceneContainer import com.android.systemui.kosmos.testScope -import com.android.systemui.res.R import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor @@ -117,6 +120,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @EnableFlags(ModesEmptyShadeFix.FLAG_NAME) + @DisableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API) fun text_changesWhenNotifsHiddenInShade() = testScope.runTest { val text by collectLastValue(underTest.text) @@ -127,7 +131,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF) runCurrent() - assertThat(text).isEqualTo(R.string.empty_shade_text) + assertThat(text).isEqualTo("No notifications") zenModeRepository.updateNotificationPolicy( suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST @@ -135,7 +139,54 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) runCurrent() - assertThat(text).isEqualTo(R.string.dnd_suppressing_shade_text) + assertThat(text).isEqualTo("Notifications paused by Do Not Disturb") + } + + @Test + @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API) + fun text_reflectsModesHidingNotifications() = + testScope.runTest { + val text by collectLastValue(underTest.text) + + assertThat(text).isEqualTo("No notifications") + + zenModeRepository.addMode( + TestModeBuilder() + .setId("Do not disturb") + .setName("Do not disturb") + .setActive(true) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false) + .build() + ) + runCurrent() + assertThat(text).isEqualTo("Notifications paused by Do not disturb") + + zenModeRepository.addMode( + TestModeBuilder() + .setId("Work") + .setName("Work") + .setActive(true) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false) + .build() + ) + runCurrent() + assertThat(text).isEqualTo("Notifications paused by Do not disturb and one other mode") + + zenModeRepository.addMode( + TestModeBuilder() + .setId("Gym") + .setName("Gym") + .setActive(true) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false) + .build() + ) + runCurrent() + assertThat(text).isEqualTo("Notifications paused by Do not disturb and 2 other modes") + + zenModeRepository.deactivateMode("Do not disturb") + zenModeRepository.deactivateMode("Work") + runCurrent() + assertThat(text).isEqualTo("Notifications paused by Gym") } @Test 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 0f6dc0723f42..c5ccf9e6a1d1 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 @@ -25,6 +25,7 @@ import android.provider.Settings.Secure.ZEN_DURATION import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import android.service.notification.SystemZenRules +import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R @@ -34,6 +35,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.shared.settings.data.repository.secureSettingsRepository +import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository import com.android.systemui.testKosmos @@ -379,4 +381,46 @@ class ZenModeInteractorTest : SysuiTestCase() { assertThat(dndMode!!.isActive).isTrue() } + + @Test + @EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API) + fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() = + testScope.runTest { + val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications) + + zenModeRepository.addModes( + listOf( + TestModeBuilder() + .setName("Not active, no list suppression") + .setActive(false) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ true) + .build(), + TestModeBuilder() + .setName("Not active, has list suppression") + .setActive(false) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false) + .build(), + TestModeBuilder() + .setName("No list suppression") + .setActive(true) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ true) + .build(), + TestModeBuilder() + .setName("Has list suppression 1") + .setActive(true) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false) + .build(), + TestModeBuilder() + .setName("Has list suppression 2") + .setActive(true) + .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false) + .build(), + ) + ) + runCurrent() + + assertThat(modesHidingNotifications?.map { it.name }) + .containsExactly("Has list suppression 1", "Has list suppression 2") + .inOrder() + } } diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ba3822bd3c23..d918cf5470c4 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1473,6 +1473,16 @@ <!-- The text to show in the notifications shade when dnd is suppressing notifications. [CHAR LIMIT=100] --> <string name="dnd_suppressing_shade_text">Notifications paused by Do Not Disturb</string> + <!-- The text to show in the notifications shade when a mode is suppressing notifications. [CHAR LIMIT=100] --> + <string name="modes_suppressing_shade_text"> + {count, plural, offset:1 + =0 {No notifications} + =1 {Notifications paused by {mode}} + =2 {Notifications paused by {mode} and one other mode} + other {Notifications paused by {mode} and # other modes} + } + </string> + <!-- Media projection permission dialog action text. [CHAR LIMIT=60] --> <string name="media_projection_action_text">Start now</string> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java index e6527032e19c..73477da247f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/view/EmptyShadeView.java @@ -39,12 +39,15 @@ import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import kotlin.Unit; +import java.util.Objects; + public class EmptyShadeView extends StackScrollerDecorView implements LaunchableView { private TextView mEmptyText; private TextView mEmptyFooterText; - private @StringRes int mText = R.string.empty_shade_text; + private @StringRes int mTextId = R.string.empty_shade_text; + private String mTextString; private @DrawableRes int mFooterIcon; private @StringRes int mFooterText; @@ -62,7 +65,9 @@ public class EmptyShadeView extends StackScrollerDecorView implements Launchable super(context, attrs); mSize = getResources().getDimensionPixelSize( R.dimen.notifications_unseen_footer_icon_size); - if (!ModesEmptyShadeFix.isEnabled()) { + if (ModesEmptyShadeFix.isEnabled()) { + mTextString = getContext().getString(R.string.empty_shade_text); + } else { // These will be set by the binder when appropriate if ModesEmptyShadeFix is on. mFooterIcon = R.drawable.ic_friction_lock_closed; mFooterText = R.string.unlock_to_see_notif_text; @@ -97,7 +102,11 @@ public class EmptyShadeView extends StackScrollerDecorView implements Launchable super.onConfigurationChanged(newConfig); mSize = getResources().getDimensionPixelSize( R.dimen.notifications_unseen_footer_icon_size); - mEmptyText.setText(mText); + if (ModesEmptyShadeFix.isEnabled()) { + mEmptyText.setText(mTextString); + } else { + mEmptyText.setText(mTextId); + } mEmptyFooterText.setVisibility(mFooterVisibility); setFooterText(mFooterText); setFooterIcon(mFooterIcon); @@ -122,11 +131,18 @@ public class EmptyShadeView extends StackScrollerDecorView implements Launchable /** Set the resource ID for the main text shown by the view. */ public void setText(@StringRes int text) { - if (ModesEmptyShadeFix.isEnabled() && mText == text) { - return; // nothing to change + ModesEmptyShadeFix.assertInLegacyMode(); + mTextId = text; + mEmptyText.setText(mTextId); + } + + /** Set the string for the main text shown by the view. */ + public void setText(String text) { + if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode() || Objects.equals(mTextString, text)) { + return; } - mText = text; - mEmptyText.setText(mText); + mTextString = text; + mEmptyText.setText(text); } /** Visibility for the footer (the additional icon+text shown below the main text). */ @@ -173,7 +189,7 @@ public class EmptyShadeView extends StackScrollerDecorView implements Launchable @StringRes public int getTextResource() { ModesEmptyShadeFix.assertInLegacyMode(); - return mText; + return mTextId; } /** Get resource ID for footer text. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt index 03b729c87aaa..d5417e7ae8f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt @@ -16,7 +16,10 @@ package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel +import android.content.Context +import android.icu.text.MessageFormat import com.android.systemui.dump.DumpManager +import com.android.systemui.modes.shared.ModesUi import com.android.systemui.res.R import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor @@ -27,6 +30,7 @@ import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.util.kotlin.FlowDumperImpl import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import java.util.Locale import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -40,6 +44,7 @@ import kotlinx.coroutines.flow.map class EmptyShadeViewModel @AssistedInject constructor( + private val context: Context, zenModeInteractor: ZenModeInteractor, seenNotificationsInteractor: SeenNotificationsInteractor, notificationSettingsInteractor: NotificationSettingsInteractor, @@ -65,16 +70,38 @@ constructor( } } - val text: Flow<Int> by lazy { + val text: Flow<String> by lazy { if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) { - flowOf(R.string.empty_shade_text) + flowOf(context.getString(R.string.empty_shade_text)) } else { - areNotificationsHiddenInShade.map { areNotificationsHiddenInShade -> - if (areNotificationsHiddenInShade) { - // TODO(b/366003631): This should reflect the current mode instead of just DND. - R.string.dnd_suppressing_shade_text - } else { - R.string.empty_shade_text + // Note: Flag modes_ui_empty_shade includes two pieces: refactoring the empty shade to + // recommended architecture, and making it so it reacts to changes for the new Modes. + // The former does not depend on the modes flags being on, but the latter does. + if (ModesUi.isEnabled) { + zenModeInteractor.modesHidingNotifications.map { modes -> + // Create a string that is either "No notifications" if no modes are filtering + // them + // out, or something like "Notifications paused by SomeMode" otherwise. + val msgFormat = + MessageFormat( + context.getString(R.string.modes_suppressing_shade_text), + Locale.getDefault(), + ) + val count = modes.count() + val args: MutableMap<String, Any> = HashMap() + args["count"] = count + if (count >= 1) { + args["mode"] = modes[0].name + } + msgFormat.format(args) + } + } else { + areNotificationsHiddenInShade.map { areNotificationsHiddenInShade -> + if (areNotificationsHiddenInShade) { + context.getString(R.string.dnd_suppressing_shade_text) + } else { + context.getString(R.string.empty_shade_text) + } } } } 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 ba45942177a2..daba1099c49d 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 @@ -20,6 +20,7 @@ import android.content.Context import android.provider.Settings import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT +import android.service.notification.ZenPolicy.VISUAL_EFFECT_NOTIFICATION_LIST import android.util.Log import androidx.concurrent.futures.await import com.android.settingslib.notification.data.repository.ZenModeRepository @@ -29,6 +30,7 @@ import com.android.settingslib.notification.modes.ZenMode import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.modes.shared.ModesUi import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository +import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository import com.android.systemui.statusbar.policy.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes @@ -39,6 +41,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -54,8 +57,8 @@ constructor( private val notificationSettingsRepository: NotificationSettingsRepository, @Background private val bgDispatcher: CoroutineDispatcher, private val iconLoader: ZenIconLoader, - private val deviceProvisioningRepository: DeviceProvisioningRepository, - private val userSetupRepository: UserSetupRepository, + deviceProvisioningRepository: DeviceProvisioningRepository, + userSetupRepository: UserSetupRepository, ) { val isZenAvailable: Flow<Boolean> = combine( @@ -126,6 +129,25 @@ constructor( val mainActiveMode: Flow<ZenModeInfo?> = activeModes.map { a -> a.mainMode }.distinctUntilChanged() + val modesHidingNotifications: Flow<List<ZenMode>> by lazy { + if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode() || !ModesUi.isEnabled) { + flowOf(listOf()) + } else { + modes + .map { modes -> + modes.filter { mode -> + mode.isActive && + !mode.policy.isVisualEffectAllowed( + /* effect = */ VISUAL_EFFECT_NOTIFICATION_LIST, + /* defaultVal = */ true, + ) + } + } + .flowOn(bgDispatcher) + .distinctUntilChanged() + } + } + suspend fun getModeIcon(mode: ZenMode): ZenIcon { return iconLoader.getIcon(context, mode).await() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt index d61dd2811daa..8fdb948e2d1d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel +import android.content.applicationContext import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor @@ -25,6 +26,7 @@ import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor val Kosmos.emptyShadeViewModel by Kosmos.Fixture { EmptyShadeViewModel( + applicationContext, zenModeInteractor, seenNotificationsInteractor, notificationSettingsInteractor, |