Add method to silence a notification instance

Allows apps to selective silence notifications
even on noisy NotificationChannels.

Test: atest
Bug: 139067153
Change-Id: I22e5fcaa93f09558160e2fe24e97e5748dba4e31
diff --git a/core/core/api/1.3.0-alpha01.txt b/core/core/api/1.3.0-alpha01.txt
index b962a95..708b5da 100644
--- a/core/core/api/1.3.0-alpha01.txt
+++ b/core/core/api/1.3.0-alpha01.txt
@@ -264,6 +264,7 @@
     field public static final int GROUP_ALERT_ALL = 0; // 0x0
     field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
     field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+    field public static final String GROUP_KEY_SILENT = "silent";
     field public static final int PRIORITY_DEFAULT = 0; // 0x0
     field public static final int PRIORITY_HIGH = 1; // 0x1
     field public static final int PRIORITY_LOW = -1; // 0xffffffff
@@ -428,6 +429,7 @@
     method public androidx.core.app.NotificationCompat.Builder! setLargeIcon(android.graphics.Bitmap!);
     method public androidx.core.app.NotificationCompat.Builder! setLights(@ColorInt int, int, int);
     method public androidx.core.app.NotificationCompat.Builder! setLocalOnly(boolean);
+    method public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
     method public androidx.core.app.NotificationCompat.Builder! setNumber(int);
     method public androidx.core.app.NotificationCompat.Builder! setOngoing(boolean);
     method public androidx.core.app.NotificationCompat.Builder! setOnlyAlertOnce(boolean);
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index b962a95..708b5da 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -264,6 +264,7 @@
     field public static final int GROUP_ALERT_ALL = 0; // 0x0
     field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
     field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+    field public static final String GROUP_KEY_SILENT = "silent";
     field public static final int PRIORITY_DEFAULT = 0; // 0x0
     field public static final int PRIORITY_HIGH = 1; // 0x1
     field public static final int PRIORITY_LOW = -1; // 0xffffffff
@@ -428,6 +429,7 @@
     method public androidx.core.app.NotificationCompat.Builder! setLargeIcon(android.graphics.Bitmap!);
     method public androidx.core.app.NotificationCompat.Builder! setLights(@ColorInt int, int, int);
     method public androidx.core.app.NotificationCompat.Builder! setLocalOnly(boolean);
+    method public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
     method public androidx.core.app.NotificationCompat.Builder! setNumber(int);
     method public androidx.core.app.NotificationCompat.Builder! setOngoing(boolean);
     method public androidx.core.app.NotificationCompat.Builder! setOnlyAlertOnce(boolean);
diff --git a/core/core/api/public_plus_experimental_1.3.0-alpha01.txt b/core/core/api/public_plus_experimental_1.3.0-alpha01.txt
index b962a95..708b5da 100644
--- a/core/core/api/public_plus_experimental_1.3.0-alpha01.txt
+++ b/core/core/api/public_plus_experimental_1.3.0-alpha01.txt
@@ -264,6 +264,7 @@
     field public static final int GROUP_ALERT_ALL = 0; // 0x0
     field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
     field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+    field public static final String GROUP_KEY_SILENT = "silent";
     field public static final int PRIORITY_DEFAULT = 0; // 0x0
     field public static final int PRIORITY_HIGH = 1; // 0x1
     field public static final int PRIORITY_LOW = -1; // 0xffffffff
@@ -428,6 +429,7 @@
     method public androidx.core.app.NotificationCompat.Builder! setLargeIcon(android.graphics.Bitmap!);
     method public androidx.core.app.NotificationCompat.Builder! setLights(@ColorInt int, int, int);
     method public androidx.core.app.NotificationCompat.Builder! setLocalOnly(boolean);
+    method public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
     method public androidx.core.app.NotificationCompat.Builder! setNumber(int);
     method public androidx.core.app.NotificationCompat.Builder! setOngoing(boolean);
     method public androidx.core.app.NotificationCompat.Builder! setOnlyAlertOnce(boolean);
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index b962a95..708b5da 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -264,6 +264,7 @@
     field public static final int GROUP_ALERT_ALL = 0; // 0x0
     field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
     field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+    field public static final String GROUP_KEY_SILENT = "silent";
     field public static final int PRIORITY_DEFAULT = 0; // 0x0
     field public static final int PRIORITY_HIGH = 1; // 0x1
     field public static final int PRIORITY_LOW = -1; // 0xffffffff
@@ -428,6 +429,7 @@
     method public androidx.core.app.NotificationCompat.Builder! setLargeIcon(android.graphics.Bitmap!);
     method public androidx.core.app.NotificationCompat.Builder! setLights(@ColorInt int, int, int);
     method public androidx.core.app.NotificationCompat.Builder! setLocalOnly(boolean);
+    method public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
     method public androidx.core.app.NotificationCompat.Builder! setNumber(int);
     method public androidx.core.app.NotificationCompat.Builder! setOngoing(boolean);
     method public androidx.core.app.NotificationCompat.Builder! setOnlyAlertOnce(boolean);
diff --git a/core/core/api/restricted_1.3.0-alpha01.txt b/core/core/api/restricted_1.3.0-alpha01.txt
index 1121902..45949e9 100644
--- a/core/core/api/restricted_1.3.0-alpha01.txt
+++ b/core/core/api/restricted_1.3.0-alpha01.txt
@@ -309,6 +309,7 @@
     field public static final int GROUP_ALERT_ALL = 0; // 0x0
     field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
     field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+    field public static final String GROUP_KEY_SILENT = "silent";
     field public static final int PRIORITY_DEFAULT = 0; // 0x0
     field public static final int PRIORITY_HIGH = 1; // 0x1
     field public static final int PRIORITY_LOW = -1; // 0xffffffff
@@ -483,6 +484,7 @@
     method public androidx.core.app.NotificationCompat.Builder! setLargeIcon(android.graphics.Bitmap!);
     method public androidx.core.app.NotificationCompat.Builder! setLights(@ColorInt int, int, int);
     method public androidx.core.app.NotificationCompat.Builder! setLocalOnly(boolean);
+    method public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
     method public androidx.core.app.NotificationCompat.Builder! setNumber(int);
     method public androidx.core.app.NotificationCompat.Builder! setOngoing(boolean);
     method public androidx.core.app.NotificationCompat.Builder! setOnlyAlertOnce(boolean);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 1121902..45949e9 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -309,6 +309,7 @@
     field public static final int GROUP_ALERT_ALL = 0; // 0x0
     field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2
     field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1
+    field public static final String GROUP_KEY_SILENT = "silent";
     field public static final int PRIORITY_DEFAULT = 0; // 0x0
     field public static final int PRIORITY_HIGH = 1; // 0x1
     field public static final int PRIORITY_LOW = -1; // 0xffffffff
@@ -483,6 +484,7 @@
     method public androidx.core.app.NotificationCompat.Builder! setLargeIcon(android.graphics.Bitmap!);
     method public androidx.core.app.NotificationCompat.Builder! setLights(@ColorInt int, int, int);
     method public androidx.core.app.NotificationCompat.Builder! setLocalOnly(boolean);
+    method public androidx.core.app.NotificationCompat.Builder setNotificationSilent();
     method public androidx.core.app.NotificationCompat.Builder! setNumber(int);
     method public androidx.core.app.NotificationCompat.Builder! setOngoing(boolean);
     method public androidx.core.app.NotificationCompat.Builder! setOnlyAlertOnce(boolean);
diff --git a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
index a79e27d..7107af0 100644
--- a/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/app/NotificationCompatTest.java
@@ -23,6 +23,7 @@
 import static androidx.core.app.NotificationCompat.GROUP_ALERT_ALL;
 import static androidx.core.app.NotificationCompat.GROUP_ALERT_CHILDREN;
 import static androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY;
+import static androidx.core.app.NotificationCompat.GROUP_KEY_SILENT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -449,6 +450,123 @@
     }
 
     @Test
+    public void testSetNotificationSilent() throws Throwable {
+
+        Notification nSummary = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setVibrate(new long[] {235})
+                .setSound(Uri.EMPTY)
+                .setDefaults(DEFAULT_ALL)
+                .setGroupSummary(true)
+                .setTicker("summary")
+                .setNotificationSilent()
+                .build();
+
+        Notification nChild = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setVibrate(new long[] {235})
+                .setSound(Uri.EMPTY)
+                .setDefaults(DEFAULT_ALL)
+                .setGroupSummary(false)
+                .setTicker("child")
+                .setNotificationSilent()
+                .build();
+
+        if (Build.VERSION.SDK_INT >= 20 && !(Build.VERSION.SDK_INT >= 26)) {
+            assertNull(nSummary.sound);
+            assertNull(nSummary.vibrate);
+            assertTrue((nSummary.defaults & DEFAULT_LIGHTS) != 0);
+            assertTrue((nSummary.defaults & DEFAULT_SOUND) == 0);
+            assertTrue((nSummary.defaults & DEFAULT_VIBRATE) == 0);
+
+            assertNull(nChild.sound);
+            assertNull(nChild.vibrate);
+            assertTrue((nChild.defaults & DEFAULT_LIGHTS) != 0);
+            assertTrue((nChild.defaults & DEFAULT_SOUND) == 0);
+            assertTrue((nChild.defaults & DEFAULT_VIBRATE) == 0);
+        }
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            assertEquals(GROUP_ALERT_SUMMARY, nChild.getGroupAlertBehavior());
+            assertEquals(GROUP_ALERT_CHILDREN, nSummary.getGroupAlertBehavior());
+            assertEquals(GROUP_KEY_SILENT, nChild.getGroup());
+            assertEquals(GROUP_KEY_SILENT, nSummary.getGroup());
+        } else if (Build.VERSION.SDK_INT >= 20) {
+            assertNull(nChild.getGroup());
+            assertNull(nSummary.getGroup());
+        }
+    }
+
+    @Test
+    public void testSetNotificationSilent_doesNotOverrideGroup() throws Throwable {
+        final String groupKey = "grouped";
+
+        Notification nSummary = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setVibrate(new long[] {235})
+                .setSound(Uri.EMPTY)
+                .setDefaults(DEFAULT_ALL)
+                .setGroupSummary(true)
+                .setGroup(groupKey)
+                .setTicker("summary")
+                .setNotificationSilent()
+                .build();
+
+        Notification nChild = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setVibrate(new long[] {235})
+                .setSound(Uri.EMPTY)
+                .setDefaults(DEFAULT_ALL)
+                .setGroupSummary(false)
+                .setGroup(groupKey)
+                .setTicker("child")
+                .setNotificationSilent()
+                .build();
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            assertEquals(GROUP_ALERT_SUMMARY, nChild.getGroupAlertBehavior());
+            assertEquals(GROUP_ALERT_CHILDREN, nSummary.getGroupAlertBehavior());
+        }
+        if (Build.VERSION.SDK_INT >= 20) {
+            assertEquals(groupKey, nChild.getGroup());
+            assertEquals(groupKey, nSummary.getGroup());
+        }
+    }
+
+    @Test
+    public void testSetNotificationSilent_notSilenced() throws Throwable {
+
+        Notification nSummary = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setVibrate(new long[] {235})
+                .setSound(Uri.EMPTY)
+                .setDefaults(DEFAULT_ALL)
+                .setGroup("grouped")
+                .setGroupSummary(true)
+                .build();
+
+        Notification nChild = new NotificationCompat.Builder(mActivityTestRule.getActivity())
+                .setVibrate(new long[] {235})
+                .setSound(Uri.EMPTY)
+                .setDefaults(DEFAULT_ALL)
+                .setGroup("grouped")
+                .setGroupSummary(false)
+                .build();
+
+        assertNotNull(nSummary.sound);
+        assertNotNull(nSummary.vibrate);
+        assertTrue((nSummary.defaults & DEFAULT_LIGHTS) != 0);
+        assertTrue((nSummary.defaults & DEFAULT_SOUND) != 0);
+        assertTrue((nSummary.defaults & DEFAULT_VIBRATE) != 0);
+
+        assertNotNull(nChild.sound);
+        assertNotNull(nChild.vibrate);
+        assertTrue((nChild.defaults & DEFAULT_LIGHTS) != 0);
+        assertTrue((nChild.defaults & DEFAULT_SOUND) != 0);
+        assertTrue((nChild.defaults & DEFAULT_VIBRATE) != 0);
+
+        if (Build.VERSION.SDK_INT >= 26) {
+            assertEquals(GROUP_ALERT_ALL, nChild.getGroupAlertBehavior());
+            assertEquals(GROUP_ALERT_ALL, nSummary.getGroupAlertBehavior());
+        }
+    }
+
+    @Test
     public void testGroupAlertBehavior_doesNotMuteIncorrectGroupNotifications() throws Throwable {
         Notification n = new NotificationCompat.Builder(mActivityTestRule.getActivity())
                 .setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompat.java b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
index 3df4449d..4364115 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompat.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
@@ -660,6 +660,13 @@
     public static final int GROUP_ALERT_CHILDREN = Notification.GROUP_ALERT_CHILDREN;
 
     /**
+     * Constant for the {@link Builder#setGroup(String) group key} that's added to notifications
+     * that are not already grouped when {@link Builder#setNotificationSilent()} is used when
+     * {@link Build.VERSION#SDK_INT} is >= {@link Build.VERSION_CODES#O}.
+     */
+    public static final String GROUP_KEY_SILENT = "silent";
+
+    /**
      * Builder class for {@link NotificationCompat} objects.  Allows easier control over
      * all the flags, as well as help constructing the typical notification layouts.
      * <p>
@@ -744,6 +751,7 @@
         boolean mAllowSystemGeneratedContextualActions;
         BubbleMetadata mBubbleMetadata;
         Notification mNotification = new Notification();
+        boolean mSilent;
 
         /**
          * @deprecated This field was not meant to be public.
@@ -864,6 +872,15 @@
         }
 
         /**
+         * Silences this instance of the notification, regardless of the sounds or vibrations set
+         * on the notification or notification channel.
+         */
+        public @NonNull Builder setNotificationSilent() {
+            mSilent = true;
+            return this;
+        }
+
+        /**
          * Set the title (first row) of the notification, in a standard notification.
          */
         public Builder setContentTitle(CharSequence title) {
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
index aac35da..135eb21 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
@@ -96,7 +96,6 @@
             mBuilder.setSubText(b.mSubText)
                     .setUsesChronometer(b.mUseChronometer)
                     .setPriority(b.mPriority);
-
             for (NotificationCompat.Action action : b.mActions) {
                 addAction(action);
             }
@@ -214,6 +213,21 @@
             mBuilder.setBubbleMetadata(
                     NotificationCompat.BubbleMetadata.toPlatform(b.mBubbleMetadata));
         }
+
+        if (b.mSilent) {
+            if (mBuilderCompat.mGroupSummary) {
+                mGroupAlertBehavior = GROUP_ALERT_CHILDREN;
+            } else {
+                mGroupAlertBehavior = GROUP_ALERT_SUMMARY;
+            }
+
+            if (Build.VERSION.SDK_INT >= 26) {
+                if (TextUtils.isEmpty(mBuilderCompat.mGroupKey)) {
+                    mBuilder.setGroup(NotificationCompat.GROUP_KEY_SILENT);
+                }
+                mBuilder.setGroupAlertBehavior(mGroupAlertBehavior);
+            }
+        }
     }
 
     @Override