Notification API hardening: add FLAG_SILENT

 Replace Notification.GROUP_KEY_SILENT with FLAG_SILENT.
 Prevents silent notifications to be forced grouped by GroupHelper.

Flag: android.service.notification.notification_silent_flag

Test: atest NotificationTest NotificationManagerServiceTest
Test: atest NotificationInterruptStateProviderImplTest
Test: atest VisualInterruptionDecisionProviderImplTest

Bug: 336488844
Change-Id: Icd0ae4d647223e481a1857f98b2732ccb52eddca
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 007fa5d..db979a5 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -762,6 +762,16 @@
     @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR)
     public static final int FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY = 0x00010000;
 
+    /**
+     * Bit to be bitwise-ored into the {@link #flags} field that should be
+     * set by the system if this notification is silent.
+     *
+     * This flag is for internal use only; applications cannot set this flag directly.
+     * @hide
+     */
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    public static final int FLAG_SILENT = 1 << 17;  //0x00020000
+
     private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
             BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
             DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
@@ -784,7 +794,8 @@
             FLAG_BUBBLE,
             FLAG_NO_DISMISS,
             FLAG_FSI_REQUESTED_BUT_DENIED,
-            FLAG_USER_INITIATED_JOB
+            FLAG_USER_INITIATED_JOB,
+            FLAG_SILENT
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface NotificationFlags{};
@@ -1692,6 +1703,7 @@
      *
      * @hide
      */
+    @Deprecated
     public static final String GROUP_KEY_SILENT = "silent";
 
     private int mGroupAlertBehavior = GROUP_ALERT_ALL;
@@ -3984,6 +3996,13 @@
             }
         }
 
+        if (android.service.notification.Flags.notificationSilentFlag()) {
+            if ((flags & FLAG_SILENT) != 0) {
+                flagStrings.add("SILENT");
+                flags &= ~FLAG_SILENT;
+            }
+        }
+
         if (flagStrings.isEmpty()) {
             return "0";
         }
@@ -4320,6 +4339,31 @@
     }
 
     /**
+     * Sets the FLAG_SILENT flag to mark the notification as silent and clears the group key.
+     * @hide
+     */
+    public void fixSilentGroup() {
+        if (android.service.notification.Flags.notificationSilentFlag()) {
+            if (GROUP_KEY_SILENT.equals(mGroupKey)) {
+                mGroupKey = null;
+                flags |= FLAG_SILENT;
+            }
+        }
+    }
+
+    /**
+     * @return whether this notification is silent. See {@link Builder#setSilent()}
+     * @hide
+     */
+    public boolean isSilent() {
+        if (android.service.notification.Flags.notificationSilentFlag()) {
+            return (flags & Notification.FLAG_SILENT) != 0;
+        } else {
+            return GROUP_KEY_SILENT.equals(getGroup()) && suppressAlertingDueToGrouping();
+        }
+    }
+
+    /**
      * Builder class for {@link Notification} objects.
      *
      * Provides a convenient way to set the various fields of a {@link Notification} and generate
@@ -4770,8 +4814,12 @@
             mN.defaults &= ~DEFAULT_VIBRATE;
             setDefaults(mN.defaults);
 
-            if (TextUtils.isEmpty(mN.mGroupKey)) {
-                setGroup(GROUP_KEY_SILENT);
+            if (android.service.notification.Flags.notificationSilentFlag()) {
+                mN.flags |= FLAG_SILENT;
+            } else {
+                if (TextUtils.isEmpty(mN.mGroupKey)) {
+                    setGroup(GROUP_KEY_SILENT);
+                }
             }
             return this;
         }
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index e9ad1c2..ef6ff05 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -84,6 +84,8 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.Spannable;
@@ -545,12 +547,26 @@
     }
 
     @Test
+    @DisableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
     public void testBuilder_setSilent_emptyGroupKey_groupKeySilent() {
         Notification emptyGroupKeyNotif = new Notification.Builder(mContext, "channelId")
                 .setGroup("")
                 .setSilent(true)
                 .build();
-        assertEquals(GROUP_KEY_SILENT, emptyGroupKeyNotif.getGroup());
+        assertThat(emptyGroupKeyNotif.getGroup()).isEqualTo(GROUP_KEY_SILENT);
+        assertThat(emptyGroupKeyNotif.isSilent()).isTrue();
+    }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    public void testBuilder_setSilent_flagSilent() {
+        final String groupKey = "groupKey";
+        Notification emptyGroupKeyNotif = new Notification.Builder(mContext, "channelId")
+            .setGroup(groupKey)
+            .setSilent(true)
+            .build();
+        assertThat(emptyGroupKeyNotif.getGroup()).isEqualTo(groupKey);
+        assertThat(emptyGroupKeyNotif.isSilent()).isTrue();
     }
 
     @Test
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 5e91786..e04e0fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -39,6 +39,7 @@
 import android.provider.Settings
 import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
 import android.provider.Settings.Global.HEADS_UP_OFF
+import android.service.notification.Flags
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage
@@ -221,6 +222,15 @@
         entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
 }
 
+class HunSilentNotificationSuppressor() :
+    VisualInterruptionFilter(
+        types = setOf(PEEK, PULSE),
+        reason = "notification isSilent"
+    ) {
+    override fun shouldSuppress(entry: NotificationEntry) =
+        entry.sbn.let { Flags.notificationSilentFlag() && it.notification.isSilent }
+}
+
 class HunJustLaunchedFsiSuppressor() :
     VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "just launched FSI") {
     override fun shouldSuppress(entry: NotificationEntry) = entry.hasJustLaunchedFullScreenIntent()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
index b77748e..576c7ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/FullScreenIntentDecisionProvider.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSED_ONLY_BY_DND
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA
 import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
+import com.android.systemui.statusbar.notification.interruption.FullScreenIntentDecisionProvider.DecisionImpl.NO_FSI_SUPPRESSIVE_SILENT_NOTIFICATION
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
@@ -94,6 +95,7 @@
             uiEventId = FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA,
             eventLogData = EventLogData("274759612", "bubbleMetadata")
         ),
+        NO_FSI_SUPPRESSIVE_SILENT_NOTIFICATION(false, "suppressive setSilent notification"),
         NO_FSI_PACKAGE_SUSPENDED(false, "package suspended"),
         FSI_DEVICE_NOT_INTERACTIVE(true, "device is not interactive"),
         FSI_DEVICE_DREAMING(true, "device is dreaming"),
@@ -154,6 +156,12 @@
             return NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
         }
 
+        if (android.service.notification.Flags.notificationSilentFlag()) {
+            if (sbn.notification.isSilent) {
+                return NO_FSI_SUPPRESSIVE_SILENT_NOTIFICATION
+            }
+        }
+
         val bubbleMetadata = notification.bubbleMetadata
         if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed) {
             return NO_FSI_SUPPRESSIVE_BUBBLE_METADATA
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index a2f97bd..e9efa56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -193,6 +193,14 @@
         })
     }
 
+    fun logNoAlertingSilentNotification(entry: NotificationEntry) {
+        buffer.log(TAG, DEBUG, {
+            str1 = entry.logKey
+        }, {
+            "No alerting: suppressed due to silent notification: $str1"
+        })
+    }
+
     fun logNoAlertingSuppressedBy(
         entry: NotificationEntry,
         suppressor: NotificationInterruptSuppressor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index c084482..2b02790 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -67,6 +67,11 @@
          */
         NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false),
         /**
+         * Notification should not FSI due to being explicitly silent.
+         * see {@link android.app.Notification#isSilent}
+         */
+        NO_FSI_SUPPRESSIVE_SILENT_NOTIFICATION(false),
+        /**
          * Device screen is off, so the FSI should launch.
          */
         FSI_DEVICE_NOT_INTERACTIVE(true),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index fea360d..450067a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -295,8 +295,17 @@
             // b/231322873: Detect and report an event when a notification has both an FSI and a
             // suppressive groupAlertBehavior, and now correctly block the FSI from firing.
             return getDecisionGivenSuppression(
-                    FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+                FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+                suppressedByDND);
+        }
+
+        // If the notification is explicitly silent, block FSI and warn.
+        if (android.service.notification.Flags.notificationSilentFlag()) {
+            if (sbn.getNotification().isSilent()) {
+                return getDecisionGivenSuppression(
+                    FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_SILENT_NOTIFICATION,
                     suppressedByDND);
+            }
         }
 
         // If the notification has suppressive BubbleMetadata, block FSI and warn.
@@ -587,8 +596,18 @@
         StatusBarNotification sbn = entry.getSbn();
 
         // Don't alert notifications that are suppressed due to group alert behavior
+        if (android.service.notification.Flags.notificationSilentFlag()) {
+            if (sbn.getNotification().isSilent()) {
+                if (log) {
+                    mLogger.logNoAlertingSilentNotification(entry);
+                }
+                return false;
+            }
+        }
+
         if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
-            if (log) mLogger.logNoAlertingGroupAlertBehavior(entry);
+            if (log)
+                mLogger.logNoAlertingGroupAlertBehavior(entry);
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 96f94ca..1c476ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -176,6 +176,7 @@
         addFilter(BubbleNotAllowedSuppressor())
         addFilter(BubbleNoMetadataSuppressor())
         addFilter(HunGroupAlertBehaviorSuppressor())
+        addFilter(HunSilentNotificationSuppressor())
         addFilter(HunJustLaunchedFsiSuppressor())
         addFilter(AlertAppSuspendedSuppressor())
         addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index bfe5c6e..61d14b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -60,6 +60,7 @@
 import android.os.PowerManager;
 import android.platform.test.annotations.DisableFlags;
 
+import android.platform.test.annotations.EnableFlags;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -244,6 +245,50 @@
     }
 
     @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    public void testShouldNotHeadsUp_silentNotification() {
+        // GIVEN state for "heads up when awake" is true
+        ensureStateForHeadsUpWhenAwake();
+
+        // WHEN the alert for a grouped notification is suppressed
+        // see {@link android.app.Notification#GROUP_ALERT_CHILDREN}
+        NotificationEntry entry = new NotificationEntryBuilder()
+            .setPkg("a")
+            .setOpPkg("a")
+            .setTag("a")
+            .setNotification(new Notification.Builder(getContext(), "a")
+                .setSilent(true)
+                .build())
+            .setImportance(IMPORTANCE_DEFAULT)
+            .build();
+
+        // THEN this entry shouldn't HUN
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+    }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    public void testShouldNotHeadsUp_silentNotificationFalse() {
+        // GIVEN state for "heads up when awake" is true
+        ensureStateForHeadsUpWhenAwake();
+
+        // WHEN the alert for a grouped notification is suppressed
+        // see {@link android.app.Notification#GROUP_ALERT_CHILDREN}
+        NotificationEntry entry = new NotificationEntryBuilder()
+            .setPkg("a")
+            .setOpPkg("a")
+            .setTag("a")
+            .setNotification(new Notification.Builder(getContext(), "a")
+                .setSilent(false)
+                .build())
+            .setImportance(IMPORTANCE_HIGH)
+            .build();
+
+        // THEN this entry shouldn't HUN
+        assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
+    }
+
+    @Test
     public void testShouldHeadsUpWhenDozing() {
         ensureStateForHeadsUpWhenDozing();
 
@@ -685,6 +730,26 @@
     }
 
     @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    public void testShouldNotFullScreen_silentNotification() {
+        Notification n = new Notification.Builder(getContext(), "a")
+            .setContentTitle("title")
+            .setContentText("content text")
+            .setFullScreenIntent(mPendingIntent, true)
+            .setSilent(true)
+            .build();
+        NotificationEntry entry = createNotification(IMPORTANCE_HIGH, n);
+
+        assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+            .isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_SILENT_NOTIFICATION);
+        assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+            .isFalse();
+        verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUPPRESSIVE_SILENT_NOTIFICATION");
+        verify(mLogger, never()).logFullscreen(any(), any());
+        verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+    }
+
+    @Test
     public void testShouldFullScreen_notInteractive() {
         NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
         Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 378705a..d5ab62b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -49,6 +49,7 @@
 import android.hardware.display.FakeAmbientDisplayConfiguration
 import android.os.Looper
 import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
 import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
 import android.provider.Settings.Global.HEADS_UP_OFF
 import android.provider.Settings.Global.HEADS_UP_ON
@@ -532,6 +533,32 @@
     }
 
     @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    fun testShouldNotHeadsUp_silentNotification() {
+        withPeekAndPulseEntry({
+                                  isGrouped = false
+                                  isGroupSummary = false
+                                  isSilent = true
+                              }) {
+            assertShouldNotHeadsUp(it)
+            assertNoEventsLogged()
+        }
+    }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    fun testShouldHeadsUp_silentNotificationFalse() {
+        withPeekAndPulseEntry({
+                                  isGrouped = false
+                                  isGroupSummary = false
+                                  isSilent = false
+                              }) {
+            assertShouldHeadsUp(it)
+            assertNoEventsLogged()
+        }
+    }
+
+    @Test
     fun testShouldNotHeadsUp_justLaunchedFsi() {
         withPeekAndPulseEntry({ hasJustLaunchedFsi = true }) {
             assertShouldNotHeadsUp(it)
@@ -1163,6 +1190,7 @@
         var groupAlertBehavior: Int? = null
         var hasBubbleMetadata = false
         var hasFsi = false
+        var isSilent = false
 
         // Set on Notification:
         var isForegroundService = false
@@ -1233,6 +1261,8 @@
                     }
                     groupAlertBehavior?.let { nb.setGroupAlertBehavior(it) }
 
+                    nb.setSilent(isSilent)
+
                     if (hasBubbleMetadata) {
                         nb.setBubbleMetadata(buildBubbleMetadata())
                     }
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index a7e14d9..614a0a5 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -604,6 +604,13 @@
             }
         }
 
+        // Suppressed because notification was explicitly flagged as silent
+        if (android.service.notification.Flags.notificationSilentFlag()) {
+            if (notification.isSilent()) {
+                return true;
+            }
+        }
+
         // Suppressed for being too recently noisy
         final String pkg = record.getSbn().getPackageName();
         if (mUsageStats.isAlertRateLimited(pkg)) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 9e49827..1c40f44 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7961,6 +7961,10 @@
                 notification.setTimeoutAfter(NOTIFICATION_TTL);
             }
         }
+
+        if (notificationForceGrouping()) {
+            notification.fixSilentGroup();
+        }
     }
 
     /**
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 3da8031..2233aa2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -84,6 +84,8 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
@@ -1221,6 +1223,44 @@
     }
 
     @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    public void testSilentNotification_flagSilent() throws Exception {
+        final Notification n = new Builder(getContext(), "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setSilent(true)
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
+                mPid, n, mUser, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn,
+                new NotificationChannel("test", "test", IMPORTANCE_HIGH));
+
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(r.isInterruptive());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
+        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+    }
+
+    @Test
+    @DisableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
+    public void testSilentNotification_groupKeySilent() throws Exception {
+        final Notification n = new Builder(getContext(), "test")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setSilent(true)
+                .build();
+        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
+                mPid, n, mUser, null, System.currentTimeMillis());
+        NotificationRecord r = new NotificationRecord(getContext(), sbn,
+                new NotificationChannel("test", "test", IMPORTANCE_HIGH));
+
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(r.isInterruptive());
+        assertEquals(-1, r.getLastAudiblyAlertedMs());
+        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+    }
+
+    @Test
     public void testHonorAlertOnlyOnceForBuzz() throws Exception {
         NotificationRecord r = getBuzzyNotification();
         NotificationRecord s = getBuzzyOnceNotification();