Merge "Allow the NAS to set a type for notifications." into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 50d97cf..5393475 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12886,7 +12886,14 @@
     field public static final String KEY_SENSITIVE_CONTENT = "key_sensitive_content";
     field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
     field public static final String KEY_TEXT_REPLIES = "key_text_replies";
+    field @FlaggedApi("android.service.notification.notification_classification") public static final String KEY_TYPE = "key_type";
     field public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
+    field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_CONTENT_RECOMMENDATION = 4; // 0x4
+    field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_NEWS = 3; // 0x3
+    field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_OTHER = 0; // 0x0
+    field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_PROMOTION = 1; // 0x1
+    field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_SOCIAL_MEDIA = 2; // 0x2
+    field @FlaggedApi("android.service.notification.notification_classification") public static final int TYPE_UNKNOWN = -1; // 0xffffffff
   }
 
   public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c2f960f..d899511 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -379,6 +379,10 @@
     method public void setImportantConversation(boolean);
     method public void setOriginalImportance(int);
     method public void setUserVisibleTaskShown(boolean);
+    field @FlaggedApi("android.service.notification.notification_classification") public static final String NEWS_ID = "android.app.news";
+    field @FlaggedApi("android.service.notification.notification_classification") public static final String PROMOTIONS_ID = "android.app.promotions";
+    field @FlaggedApi("android.service.notification.notification_classification") public static final String RECS_ID = "android.app.recs";
+    field @FlaggedApi("android.service.notification.notification_classification") public static final String SOCIAL_MEDIA_ID = "android.app.social";
   }
 
   public final class NotificationChannelGroup implements android.os.Parcelable {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3f6c81b..326d7ce 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -72,6 +72,35 @@
     public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
 
     /**
+     * A reserved id for a system channel reserved for promotional notifications.
+     *  @hide
+     */
+    @TestApi
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final String PROMOTIONS_ID = "android.app.promotions";
+    /**
+     * A reserved id for a system channel reserved for non-conversation social media notifications.
+     *  @hide
+     */
+    @TestApi
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final String SOCIAL_MEDIA_ID = "android.app.social";
+    /**
+     * A reserved id for a system channel reserved for news notifications.
+     *  @hide
+     */
+    @TestApi
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final String NEWS_ID = "android.app.news";
+    /**
+     * A reserved id for a system channel reserved for content recommendation notifications.
+     *  @hide
+     */
+    @TestApi
+    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final String RECS_ID = "android.app.recs";
+
+    /**
      * The formatter used by the system to create an id for notification
      * channels when it automatically creates conversation channels on behalf of an app. The format
      * string takes two arguments, in this order: the
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 9c14946..63410c7 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -15,6 +15,8 @@
  */
 package android.service.notification;
 
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
@@ -62,7 +64,8 @@
             KEY_IMPORTANCE_PROPOSAL,
             KEY_SENSITIVE_CONTENT,
             KEY_RANKING_SCORE,
-            KEY_NOT_CONVERSATION
+            KEY_NOT_CONVERSATION,
+            KEY_TYPE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Keys {}
@@ -172,6 +175,59 @@
     public static final String KEY_NOT_CONVERSATION = "key_not_conversation";
 
     /**
+     * Data type: int, the classification type of this notification. The OS may display
+     * notifications differently depending on the type, and may change the alerting level of the
+     * notification.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final String KEY_TYPE = "key_type";
+
+    /** @hide */
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_UNKNOWN,
+            TYPE_OTHER,
+            TYPE_PROMOTION,
+            TYPE_SOCIAL_MEDIA,
+            TYPE_NEWS,
+            TYPE_CONTENT_RECOMMENDATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Types {}
+
+    /**
+     * The type of this notification is unknown.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final int TYPE_UNKNOWN = -1;
+    /**
+     * The type of this notification is not one of ones known to the NotificationAssistantService.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final int TYPE_OTHER = 0;
+    /**
+     * The type of this notification is a promotion/deal.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final int TYPE_PROMOTION = 1;
+    /**
+     * The type of this notification is social media content that isn't a
+     * {@link Notification.Builder#setShortcutId(String) conversation}.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final int TYPE_SOCIAL_MEDIA = 2;
+    /**
+     * The type of this notification is news.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final int TYPE_NEWS = 3;
+    /**
+     * The type of this notification is content recommendation, for example new videos or books the
+     * user may be interested in.
+     */
+    @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public static final int TYPE_CONTENT_RECOMMENDATION = 4;
+
+    /**
      * Create a notification adjustment.
      *
      * @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
index c5b4b41..bdef041 100644
--- a/core/java/android/service/notification/flags.aconfig
+++ b/core/java/android/service/notification/flags.aconfig
@@ -35,4 +35,12 @@
   description: "Guards the new CallStyleNotificationEventsCallback"
   bug: "305095040"
   is_fixed_read_only: true
+}
+
+flag {
+    name: "notification_classification"
+    is_exported: true
+    namespace: "systemui"
+    description: "Allows the NAS to classify notifications"
+    bug: "343988084"
 }
\ No newline at end of file
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b6e8383..351cbad 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5430,6 +5430,10 @@
     <string name="call_notification_screening_text">Screening an incoming call</string>
 
     <string name="default_notification_channel_label">Uncategorized</string>
+    <string name="promotional_notification_channel_label">Promotions</string>
+    <string name="social_notification_channel_label">Social</string>
+    <string name="news_notification_channel_label">News</string>
+    <string name="recs_notification_channel_label">Recommendations</string>
 
     <string name="importance_from_user">You set the importance of these notifications.</string>
     <string name="importance_from_person">This is important because of the people involved.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 85397fa..0de9179 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5516,6 +5516,12 @@
   <java-symbol type="string" name="biometric_dangling_notification_action_set_up" />
   <java-symbol type="string" name="biometric_dangling_notification_action_not_now" />
 
+  <!-- Notification bundles -->
+  <java-symbol type="string"  name="promotional_notification_channel_label"/>
+  <java-symbol type="string"  name="social_notification_channel_label"/>
+  <java-symbol type="string"  name="news_notification_channel_label"/>
+  <java-symbol type="string"  name="recs_notification_channel_label"/>
+
   <!-- Priority Modes icons -->
   <java-symbol type="drawable" name="ic_zen_mode_type_bedtime" />
   <java-symbol type="drawable" name="ic_zen_mode_type_driving" />
@@ -5526,5 +5532,4 @@
   <java-symbol type="drawable" name="ic_zen_mode_type_schedule_time" />
   <java-symbol type="drawable" name="ic_zen_mode_type_theater" />
   <java-symbol type="drawable" name="ic_zen_mode_type_unknown" />
-
 </resources>
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b436c8b..6c09359 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -46,6 +46,10 @@
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
+import static android.app.NotificationChannel.NEWS_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
 import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
 import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED;
@@ -98,6 +102,11 @@
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_NULL;
 import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Adjustment.KEY_TYPE;
+import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
+import static android.service.notification.Adjustment.TYPE_NEWS;
+import static android.service.notification.Adjustment.TYPE_PROMOTION;
+import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA;
 import static android.service.notification.Flags.callstyleCallbackApi;
 import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners;
 import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle;
@@ -458,7 +467,8 @@
             Adjustment.KEY_IMPORTANCE_PROPOSAL,
             Adjustment.KEY_SENSITIVE_CONTENT,
             Adjustment.KEY_RANKING_SCORE,
-            Adjustment.KEY_NOT_CONVERSATION
+            Adjustment.KEY_NOT_CONVERSATION,
+            Adjustment.KEY_TYPE
     };
 
     static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] {
@@ -6608,6 +6618,30 @@
             for (String removeKey : toRemove) {
                 adjustments.remove(removeKey);
             }
+            if (android.service.notification.Flags.notificationClassification()
+                    && adjustments.containsKey(KEY_TYPE)) {
+                NotificationChannel newChannel = null;
+                int type = adjustments.getInt(KEY_TYPE);
+                if (TYPE_NEWS == type) {
+                    newChannel = mPreferencesHelper.getNotificationChannel(
+                            r.getSbn().getPackageName(), r.getUid(), NEWS_ID, false);
+                } else if (TYPE_PROMOTION == type) {
+                    newChannel = mPreferencesHelper.getNotificationChannel(
+                            r.getSbn().getPackageName(), r.getUid(), PROMOTIONS_ID, false);
+                } else if (TYPE_SOCIAL_MEDIA == type) {
+                    newChannel = mPreferencesHelper.getNotificationChannel(
+                            r.getSbn().getPackageName(), r.getUid(), SOCIAL_MEDIA_ID, false);
+                } else if (TYPE_CONTENT_RECOMMENDATION == type) {
+                    newChannel = mPreferencesHelper.getNotificationChannel(
+                            r.getSbn().getPackageName(), r.getUid(), RECS_ID, false);
+                }
+                if (newChannel == null) {
+                    adjustments.remove(KEY_TYPE);
+                } else {
+                    // swap app provided type with the real thing
+                    adjustments.putParcelable(KEY_TYPE, newChannel);
+                }
+            }
             r.addAdjustment(adjustment);
             if (adjustment.getSignals().containsKey(Adjustment.KEY_SENSITIVE_CONTENT)) {
                 logSensitiveAdjustmentReceived(isPosted,
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 0c6a6c8..0d4bdf6 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -710,7 +710,8 @@
                 if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
                     final ArrayList<SnoozeCriterion> snoozeCriterionList =
                             adjustment.getSignals().getParcelableArrayList(
-                                    Adjustment.KEY_SNOOZE_CRITERIA, android.service.notification.SnoozeCriterion.class);
+                                    Adjustment.KEY_SNOOZE_CRITERIA,
+                                    android.service.notification.SnoozeCriterion.class);
                     setSnoozeCriteria(snoozeCriterionList);
                     EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA,
                             snoozeCriterionList.toString());
@@ -736,7 +737,8 @@
                 }
                 if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) {
                     setSystemGeneratedSmartActions(
-                            signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, android.app.Notification.Action.class));
+                            signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS,
+                                    android.app.Notification.Action.class));
                     EventLogTags.writeNotificationAdjusted(getKey(),
                             Adjustment.KEY_CONTEXTUAL_ACTIONS,
                             getSystemGeneratedSmartActions().toString());
@@ -778,6 +780,14 @@
                             Adjustment.KEY_SENSITIVE_CONTENT,
                             Boolean.toString(mSensitiveContent));
                 }
+                if (android.service.notification.Flags.notificationClassification()
+                        && signals.containsKey(Adjustment.KEY_TYPE)) {
+                    updateNotificationChannel(signals.getParcelable(Adjustment.KEY_TYPE,
+                            NotificationChannel.class));
+                    EventLogTags.writeNotificationAdjusted(getKey(),
+                            Adjustment.KEY_TYPE,
+                            mChannel.getId());
+                }
                 if (!signals.isEmpty() && adjustment.getIssuer() != null) {
                     mAdjustmentIssuer = adjustment.getIssuer();
                 }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 309e945..9ee05d8 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -22,11 +22,14 @@
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
 import static android.os.UserHandle.USER_SYSTEM;
+import static android.service.notification.Flags.notificationClassification;
+
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -514,6 +517,10 @@
                 Slog.e(TAG, "createDefaultChannelIfNeededLocked - Exception: " + e);
             }
 
+            if (notificationClassification()) {
+                addReservedChannelsLocked(r);
+            }
+
             if (r.uid == UNKNOWN_UID) {
                 if (Flags.persistIncompleteRestoreData()) {
                     r.userId = userId;
@@ -603,6 +610,40 @@
         return true;
     }
 
+    private void addReservedChannelsLocked(PackagePreferences p) {
+        if (!p.channels.containsKey(NotificationChannel.PROMOTIONS_ID)) {
+            NotificationChannel channel = new NotificationChannel(
+                    NotificationChannel.PROMOTIONS_ID,
+                    mContext.getString(R.string.promotional_notification_channel_label),
+                    IMPORTANCE_LOW);
+            p.channels.put(channel.getId(), channel);
+        }
+
+        if (!p.channels.containsKey(NotificationChannel.RECS_ID)) {
+            NotificationChannel channel = new NotificationChannel(
+                    NotificationChannel.RECS_ID,
+                    mContext.getString(R.string.recs_notification_channel_label),
+                    IMPORTANCE_LOW);
+            p.channels.put(channel.getId(), channel);
+        }
+
+        if (!p.channels.containsKey(NotificationChannel.NEWS_ID)) {
+            NotificationChannel channel = new NotificationChannel(
+                    NotificationChannel.NEWS_ID,
+                    mContext.getString(R.string.news_notification_channel_label),
+                    IMPORTANCE_LOW);
+            p.channels.put(channel.getId(), channel);
+        }
+
+        if (!p.channels.containsKey(NotificationChannel.SOCIAL_MEDIA_ID)) {
+            NotificationChannel channel = new NotificationChannel(
+                    NotificationChannel.SOCIAL_MEDIA_ID,
+                    mContext.getString(R.string.social_notification_channel_label),
+                    IMPORTANCE_LOW);
+            p.channels.put(channel.getId(), channel);
+        }
+    }
+
     public void writeXml(TypedXmlSerializer out, boolean forBackup, int userId) throws IOException {
         out.startTag(null, TAG_RANKING);
         out.attributeInt(null, ATT_VERSION, XML_VERSION);
@@ -1801,7 +1842,7 @@
     public boolean onlyHasDefaultChannel(String pkg, int uid) {
         synchronized (mPackagePreferences) {
             PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
-            if (r.channels.size() == 1
+            if (r.channels.size() == (notificationClassification() ? 5 : 1)
                     && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
                 return true;
             }
@@ -2611,6 +2652,7 @@
                                 context.getResources().getString(
                                         R.string.default_notification_channel_label));
                     }
+                    // TODO (b/346396459): Localize all reserved channels
                 }
             }
         }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 398dc281..c1d7afb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -40,6 +40,7 @@
 import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
 import static android.app.Notification.FLAG_USER_INITIATED_JOB;
 import static android.app.Notification.VISIBILITY_PRIVATE;
+import static android.app.NotificationChannel.NEWS_ID;
 import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
 import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
@@ -91,7 +92,9 @@
 import static android.service.notification.Adjustment.KEY_CONTEXTUAL_ACTIONS;
 import static android.service.notification.Adjustment.KEY_IMPORTANCE;
 import static android.service.notification.Adjustment.KEY_TEXT_REPLIES;
+import static android.service.notification.Adjustment.KEY_TYPE;
 import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
+import static android.service.notification.Adjustment.TYPE_NEWS;
 import static android.service.notification.Condition.SOURCE_CONTEXT;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
 import static android.service.notification.Condition.STATE_TRUE;
@@ -15778,4 +15781,27 @@
         when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false);
         assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component);
     }
+
+    @Test
+    @EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testApplyAdjustment_keyType_validType() throws Exception {
+        final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+        NotificationManagerService.WorkerHandler handler = mock(
+                NotificationManagerService.WorkerHandler.class);
+        mService.setHandler(handler);
+
+        Bundle signals = new Bundle();
+        signals.putInt(KEY_TYPE, TYPE_NEWS);
+        Adjustment adjustment = new Adjustment(
+                r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier());
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
+        mBinderService.applyAdjustmentFromAssistant(null, adjustment);
+
+        waitForIdle();
+
+        r.applyAdjustments();
+
+        assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index d1880d2..ff4bc21 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -23,6 +23,10 @@
 import static android.app.NotificationChannel.ALLOW_BUBBLE_ON;
 import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
 import static android.app.NotificationChannel.DEFAULT_ALLOW_BUBBLE;
+import static android.app.NotificationChannel.NEWS_ID;
+import static android.app.NotificationChannel.PROMOTIONS_ID;
+import static android.app.NotificationChannel.RECS_ID;
+import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
 import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
 import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE;
 import static android.app.NotificationChannel.USER_LOCKED_LIGHTS;
@@ -47,6 +51,9 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
+import static android.service.notification.Flags.notificationClassification;
+
 import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
@@ -184,6 +191,7 @@
 
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4.class)
+@EnableFlags(FLAG_PERSIST_INCOMPLETE_RESTORE_DATA)
 public class PreferencesHelperTest extends UiServiceTestCase {
     private static final int UID_HEADLESS = 1000000;
     private static final UserHandle USER = UserHandle.of(0);
@@ -233,7 +241,7 @@
     @Parameters(name = "{0}")
     public static List<FlagsParameterization> getParams() {
         return FlagsParameterization.allCombinationsOf(
-                FLAG_PERSIST_INCOMPLETE_RESTORE_DATA);
+                FLAG_NOTIFICATION_CLASSIFICATION);
     }
 
     public PreferencesHelperTest(FlagsParameterization flags) {
@@ -582,6 +590,16 @@
         assertTrue(mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2, false, false,
                 UID_N_MR1, false));
 
+        NotificationChannel updateNews = null;
+        if (notificationClassification()) {
+            // change one of the reserved bundle channels to ensure changes are persisted across
+            // boot
+            updateNews = mHelper.getNotificationChannel(
+                    PKG_N_MR1, UID_N_MR1, NEWS_ID, false).copy();
+            updateNews.setImportance(IMPORTANCE_NONE);
+            mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews, true, 1000, true);
+        }
+
         mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
 
         ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
@@ -597,6 +615,12 @@
                 mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
         compareChannels(channel2,
                 mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel2.getId(), false));
+        if (notificationClassification()) {
+            assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews.getId(),
+                    false)).isNotNull();
+            assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, updateNews.getId(),
+                            false)).isEqualTo(updateNews);
+        }
 
         List<NotificationChannelGroup> actualGroups = mXmlHelper.getNotificationChannelGroups(
                 PKG_N_MR1, UID_N_MR1, false, true, false, true, null).getList();
@@ -1130,9 +1154,22 @@
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
                 + "=\"false\" uid=\"10002\">\n"
+                + (notificationClassification() ? "<channel id=\"android.app.social\" "
+                + "name=\"Social\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id\" name=\"name\" importance=\"2\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "</package>\n"
                 + "<package name=\"com.example.n_mr1\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
@@ -1140,6 +1177,10 @@
                 + "=\"false\" uid=\"10001\">\n"
                 + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
                 + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.social\" "
+                + "name=\"Social\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
                 + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1149,14 +1190,22 @@
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
                 + "</package>\n"
                 + "<package name=\"com.example.p\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
-                + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" uid=\"10003\" />\n"
-                + "</ranking>";
+                + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" uid=\"10003\"";
         assertThat(baos.toString()).contains(expected);
     }
 
@@ -1216,9 +1265,22 @@
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
                 + "=\"false\">\n"
+                + (notificationClassification() ? "<channel id=\"android.app.social\" "
+                + "name=\"Social\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id\" name=\"name\" importance=\"2\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "</package>\n"
                 // Importance default because on in permission helper
                 + "<package name=\"com.example.n_mr1\" importance=\"3\" show_badge=\"true\" "
@@ -1227,6 +1289,10 @@
                 + "=\"false\">\n"
                 + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
                 + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.social\" "
+                + "name=\"Social\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
                 + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1236,6 +1302,15 @@
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1243,12 +1318,11 @@
                 // Importance default because on in permission helper
                 + "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
-                + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" />\n"
-                // Packages that exist solely in permissionhelper
-                + "<package name=\"first\" importance=\"3\" />\n"
-                + "<package name=\"third\" importance=\"0\" />\n"
-                + "</ranking>";
+                + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\"";
         assertThat(baos.toString()).contains(expected);
+        // Packages that exist solely in permissionhelper
+        assertThat(baos.toString()).contains("<package name=\"first\" importance=\"3\"");
+        assertThat(baos.toString()).contains("<package name=\"third\" importance=\"0\"");
     }
 
     @Test
@@ -1303,9 +1377,22 @@
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" "
                 + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" sent_valid_bubble"
                 + "=\"false\">\n"
+                + (notificationClassification() ? "<channel id=\"android.app.social\" "
+                + "name=\"Social\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id\" name=\"name\" importance=\"2\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" orig_imp=\"2\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "</package>\n"
                 // Importance 0 because missing from permission helper
                 + "<package name=\"com.example.n_mr1\" importance=\"0\" show_badge=\"true\" "
@@ -1314,6 +1401,10 @@
                 + "=\"false\">\n"
                 + "<channelGroup id=\"1\" name=\"bye\" blocked=\"false\" locked=\"0\" />\n"
                 + "<channelGroup id=\"2\" name=\"hello\" blocked=\"false\" locked=\"0\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.social\" "
+                + "name=\"Social\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"id1\" name=\"name1\" importance=\"4\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
                 + "<channel id=\"id2\" name=\"name2\" desc=\"descriptions for all\" "
@@ -1323,6 +1414,15 @@
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" vibration_enabled=\"true\" show_badge=\"true\" "
                 + "orig_imp=\"4\" />\n"
+                + (notificationClassification() ? "<channel id=\"android.app.news\" name=\"News\" "
+                + "importance=\"2\" sound=\"content://settings/system/notification_sound\" "
+                + "usage=\"5\" content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.recs\" name=\"Recommendations\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "<channel id=\"android.app.promotions\" name=\"Promotions\" importance=\"2\" "
+                + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" : "")
                 + "<channel id=\"miscellaneous\" name=\"Uncategorized\" "
                 + "sound=\"content://settings/system/notification_sound\" usage=\"5\" "
                 + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
@@ -1330,8 +1430,7 @@
                 // Importance default because on in permission helper
                 + "<package name=\"com.example.p\" importance=\"3\" show_badge=\"true\" "
                 + "app_user_locked_fields=\"0\" sent_invalid_msg=\"true\" sent_valid_msg=\"true\""
-                + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\" />\n"
-                + "</ranking>";
+                + " user_demote_msg_app=\"true\" sent_valid_bubble=\"false\"";
         assertThat(baos.toString()).contains(expected);
     }
 
@@ -1851,8 +1950,8 @@
         parser.nextTag();
         mHelper.readXml(parser, false, UserHandle.USER_ALL);
 
-        final NotificationChannel updated1 =
-            mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+        final NotificationChannel updated1 = mHelper.getNotificationChannel(
+                PKG_N_MR1, UID_N_MR1, NotificationChannel.DEFAULT_CHANNEL_ID, false);
         assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
         assertTrue(updated1.canBypassDnd());
         assertEquals(VISIBILITY_SECRET, updated1.getLockscreenVisibility());
@@ -2392,18 +2491,20 @@
         // Returns only non-deleted channels
         List<NotificationChannel> channels =
                 mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList();
-        assertEquals(2, channels.size());   // Default channel + non-deleted channel
+        // Default channel + non-deleted channel + system defaults
+        assertEquals(notificationClassification() ? 6 : 2, channels.size());
         for (NotificationChannel nc : channels) {
-            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+            if (channel2.getId().equals(nc.getId())) {
                 compareChannels(channel2, nc);
             }
         }
 
         // Returns deleted channels too
         channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList();
-        assertEquals(3, channels.size());               // Includes default channel
+        // Includes system channel(s)
+        assertEquals(notificationClassification() ? 7 : 3, channels.size());
         for (NotificationChannel nc : channels) {
-            if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+            if (channel2.getId().equals(nc.getId())) {
                 compareChannels(channelMap.get(nc.getId()), nc);
             }
         }
@@ -3013,7 +3114,8 @@
 
             final ApplicationInfo legacy = new ApplicationInfo();
             legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
-            when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy);
+            when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt()))
+                    .thenReturn(legacy);
 
             // create records with the default channel for all user 0 and user 1 uids
             mHelper.canShowBadge(PKG_N_MR1, user0Uids[i]);
@@ -3024,13 +3126,14 @@
 
         // user 0 records remain
         for (int i = 0; i < user0Uids.length; i++) {
-            assertEquals(1,
-                    mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false).getList().size());
+            assertEquals(notificationClassification() ? 5 : 1,
+                    mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false)
+                            .getList().size());
         }
         // user 1 records are gone
         for (int i = 0; i < user1Uids.length; i++) {
-            assertEquals(0,
-                    mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false).getList().size());
+            assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false)
+                    .getList().size());
         }
     }
 
@@ -3054,7 +3157,8 @@
 
         assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM,
                 new String[]{PKG_N_MR1}, new int[]{UID_N_MR1}));
-        assertEquals(2, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
+        assertEquals(notificationClassification() ? 6 : 2,
+                mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
     }
 
     @Test
@@ -3126,7 +3230,8 @@
     @Test
     public void testRecordDefaults() throws Exception {
         assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
-        assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
+        assertEquals(notificationClassification() ? 5 : 1,
+                mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size());
     }
 
     @Test
@@ -3212,7 +3317,9 @@
         assertEquals(3, actual.size());
         for (NotificationChannelGroup group : actual) {
             if (group.getId() == null) {
-                assertEquals(2, group.getChannels().size()); // misc channel too
+                assertEquals(
+                        notificationClassification() ? 6 : 2,
+                        group.getChannels().size()); // misc channel too
                 assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
                         || channel3.getId().equals(group.getChannels().get(1).getId()));
             } else if (group.getId().equals(ncg.getId())) {
@@ -3363,6 +3470,9 @@
                         new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false,
                         UID_N_MR1, false);
             }
+            if (notificationClassification()) {
+                numChannels += 4;
+            }
             expectedChannels.put(pkgName, numChannels);
         }
 
@@ -4583,7 +4693,12 @@
 
     @Test
     public void testTooManyChannels() {
-        for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) {
+        int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
+        if (notificationClassification()) {
+            // reserved channels lower limit
+            numToCreate -= 4;
+        }
+        for (int i = 0; i < numToCreate; i++) {
             NotificationChannel channel = new NotificationChannel(String.valueOf(i),
                     String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
             mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true, UID_O, false);
@@ -4602,11 +4717,16 @@
 
     @Test
     public void testTooManyChannels_xml() throws Exception {
+        int numToCreate = NOTIFICATION_CHANNEL_COUNT_LIMIT;
+        if (notificationClassification()) {
+            // reserved channels lower limit
+            numToCreate -= 4;
+        }
         String extraChannel = "EXTRA";
         String extraChannel1 = "EXTRA1";
 
         // create first... many... directly so we don't need a big xml blob in this test
-        for (int i = 0; i < NOTIFICATION_CHANNEL_COUNT_LIMIT; i++) {
+        for (int i = 0; i < numToCreate; i++) {
             NotificationChannel channel = new NotificationChannel(String.valueOf(i),
                     String.valueOf(i), NotificationManager.IMPORTANCE_HIGH);
             mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, true, UID_O, false);
@@ -5619,8 +5739,9 @@
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        // number of channels with preferences should be 3 total
-        assertEquals("expected number of events", 3, events.size());
+        assertEquals("expected number of events",
+                notificationClassification() ? 7 : 3,
+                events.size());
         for (StatsEvent ev : events) {
             // all of these events should be of PackageNotificationChannelPreferences type,
             // and therefore we expect the atom to have this field.
@@ -5643,7 +5764,7 @@
             } else if (eventChannelId.equals("a")) {
                 assertEquals("channel name", "a", p.getChannelName());
                 assertEquals("importance", IMPORTANCE_LOW, p.getImportance());
-            } else { // b
+            } else if (eventChannelId.equals("b")){ // b
                 assertEquals("channel name", "b", p.getChannelName());
                 assertEquals("importance", IMPORTANCE_HIGH, p.getImportance());
             }
@@ -5661,11 +5782,17 @@
         mHelper.createNotificationChannel(PKG_O, UID_O, channelC, true, false, UID_O, false);
 
         List<String> channels = new LinkedList<>(Arrays.asList("a", "b", "c"));
+        if (notificationClassification()) {
+            channels.add(NEWS_ID);
+            channels.add(PROMOTIONS_ID);
+            channels.add(SOCIAL_MEDIA_ID);
+            channels.add(RECS_ID);
+        }
 
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        assertEquals("total events", 3, events.size());
+        assertEquals("total events", notificationClassification() ? 7 : 3, events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5699,7 +5826,7 @@
         mHelper.pullPackageChannelPreferencesStats(events);
 
         // In this case, we want to check the properties of the conversation channel (not parent)
-        assertEquals("total events", 2, events.size());
+        assertEquals("total events", notificationClassification() ? 6 : 2, events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5731,7 +5858,9 @@
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        assertEquals("total events", 2, events.size());
+        assertEquals("total events",
+                notificationClassification() ? 6 : 2,
+                events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -5762,7 +5891,9 @@
         ArrayList<StatsEvent> events = new ArrayList<>();
         mHelper.pullPackageChannelPreferencesStats(events);
 
-        assertEquals("total events", 2, events.size());
+        assertEquals("total events",
+                notificationClassification() ? 6 : 2,
+                events.size());
         for (StatsEvent ev : events) {
             AtomsProto.Atom atom = StatsEventTestUtils.convertToAtom(ev);
             assertTrue(atom.hasPackageNotificationChannelPreferences());
@@ -6044,6 +6175,23 @@
         assertEquals(0, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count());
     }
 
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testNotificationBundles() {
+        // do something that triggers settings creation for an app
+        mHelper.setShowBadge(PKG_O, UID_O, true);
+
+        // verify 4 reserved channels are created
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance())
+                .isEqualTo(IMPORTANCE_LOW);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, false)
+                .getImportance()).isEqualTo(IMPORTANCE_LOW);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID, false)
+                .getImportance()).isEqualTo(IMPORTANCE_LOW);
+        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, false).getImportance())
+                .isEqualTo(IMPORTANCE_LOW);
+    }
+
     private static NotificationChannel cloneChannel(NotificationChannel original) {
         Parcel parcel = Parcel.obtain();
         try {