Merge "Adds bulk getChannelsBypassingDnd call to NMS" into main
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 5bc0ddc..e2bee64 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -122,6 +122,7 @@
     boolean onlyHasDefaultChannel(String pkg, int uid);
     boolean areChannelsBypassingDnd();
     ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int uid);
+    List<String> getPackagesBypassingDnd(int userId, boolean includeConversationChannels);
     boolean isPackagePaused(String pkg);
     void deleteNotificationHistoryItem(String pkg, int uid, long postedTime);
     boolean isPermissionFixed(String pkg, int userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4023e52..b436c8b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -225,6 +225,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
 import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -4536,6 +4537,34 @@
         }
 
         @Override
+        public List<String> getPackagesBypassingDnd(int userId,
+                                                    boolean includeConversationChannels) {
+            checkCallerIsSystem();
+
+            final ArraySet<String> packageNames = new ArraySet<>();
+
+            for (int user : mUm.getProfileIds(userId, false)) {
+                List<PackageInfo> pkgs = mPackageManagerClient.getInstalledPackagesAsUser(0, user);
+                for (PackageInfo pi : pkgs) {
+                    String pkg = pi.packageName;
+                    // If any NotificationChannel for this package is bypassing, the
+                    // package is considered bypassing.
+                    for (NotificationChannel channel : getNotificationChannelsBypassingDnd(pkg,
+                            pi.applicationInfo.uid).getList()) {
+                        // Skips non-demoted conversation channels.
+                        if (!includeConversationChannels
+                                && !TextUtils.isEmpty(channel.getConversationId())
+                                && !channel.isDemoted()) {
+                            continue;
+                        }
+                        packageNames.add(pkg);
+                    }
+                }
+            }
+            return new ArrayList<String>(packageNames);
+        }
+
+        @Override
         public boolean areChannelsBypassingDnd() {
             if (android.app.Flags.modesApi()) {
                 return mZenModeHelper.getConsolidatedNotificationPolicy().allowPriorityChannels()
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 f07e5bc..398dc281 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -208,6 +208,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.LauncherApps;
 import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
@@ -12072,6 +12073,127 @@
     }
 
     @Test
+    public void testGetPackagesBypassingDnd_empty() throws RemoteException {
+        mService.setPreferencesHelper(mPreferencesHelper);
+        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
+        assertThat(result).isEmpty();
+    }
+
+    @Test
+    public void testGetPackagesBypassingDnd_excludeConversationChannels() throws RemoteException {
+        mService.setPreferencesHelper(mPreferencesHelper);
+
+        // Set packages
+        PackageInfo pkg0 = new PackageInfo();
+        pkg0.packageName = "pkg0";
+        pkg0.applicationInfo = new ApplicationInfo();
+        pkg0.applicationInfo.uid = mUid;
+        PackageInfo pkg1 = new PackageInfo();
+        pkg1.packageName = "pkg1";
+        pkg1.applicationInfo = new ApplicationInfo();
+        pkg1.applicationInfo.uid = mUid;
+        PackageInfo pkg2 = new PackageInfo();
+        pkg2.packageName = "pkg2";
+        pkg2.applicationInfo = new ApplicationInfo();
+        pkg2.applicationInfo.uid = mUid;
+
+        when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
+                .thenReturn(List.of(pkg0, pkg1, pkg2));
+
+        // Conversation channels
+        NotificationChannel nc0 = new NotificationChannel("id0", "id0",
+                NotificationManager.IMPORTANCE_HIGH);
+        nc0.setConversationId("parentChannel", "conversationId");
+
+        // Demoted conversation channel
+        NotificationChannel nc1 = new NotificationChannel("id1", "id1",
+                NotificationManager.IMPORTANCE_HIGH);
+        nc1.setConversationId("parentChannel", "conversationId");
+        nc1.setDemoted(true);
+
+        // Non-conversation channels
+        NotificationChannel nc2 = new NotificationChannel("id2", "id2",
+                NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel nc3 = new NotificationChannel("id3", "id3",
+                NotificationManager.IMPORTANCE_HIGH);
+
+        ParceledListSlice<NotificationChannel> pls0 =
+                new ParceledListSlice(ImmutableList.of(nc0));
+        ParceledListSlice<NotificationChannel> pls1 =
+                new ParceledListSlice(ImmutableList.of(nc1));
+        ParceledListSlice<NotificationChannel> pls2 =
+                new ParceledListSlice(ImmutableList.of(nc2, nc3));
+
+        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
+                .thenReturn(pls0);
+        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
+                .thenReturn(pls1);
+        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
+                .thenReturn(pls2);
+
+        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, false);
+
+        assertThat(result).containsExactly("pkg1", "pkg2");
+    }
+
+    @Test
+    public void testGetPackagesBypassingDnd_includeConversationChannels() throws RemoteException {
+        mService.setPreferencesHelper(mPreferencesHelper);
+
+        // Set packages
+        PackageInfo pkg0 = new PackageInfo();
+        pkg0.packageName = "pkg0";
+        pkg0.applicationInfo = new ApplicationInfo();
+        pkg0.applicationInfo.uid = mUid;
+        PackageInfo pkg1 = new PackageInfo();
+        pkg1.packageName = "pkg1";
+        pkg1.applicationInfo = new ApplicationInfo();
+        pkg1.applicationInfo.uid = mUid;
+        PackageInfo pkg2 = new PackageInfo();
+        pkg2.packageName = "pkg2";
+        pkg2.applicationInfo = new ApplicationInfo();
+        pkg2.applicationInfo.uid = mUid;
+
+        when(mPackageManagerClient.getInstalledPackagesAsUser(0, mUserId))
+                .thenReturn(List.of(pkg0, pkg1, pkg2));
+
+        // Conversation channels
+        NotificationChannel nc0 = new NotificationChannel("id0", "id0",
+                NotificationManager.IMPORTANCE_HIGH);
+        nc0.setConversationId("parentChannel", "conversationId");
+
+        // Demoted conversation channel
+        NotificationChannel nc1 = new NotificationChannel("id1", "id1",
+                NotificationManager.IMPORTANCE_HIGH);
+        nc1.setConversationId("parentChannel", "conversationId");
+        nc1.setDemoted(true);
+
+        // Non-conversation channels
+        NotificationChannel nc2 = new NotificationChannel("id2", "id2",
+                NotificationManager.IMPORTANCE_HIGH);
+        NotificationChannel nc3 = new NotificationChannel("id3", "id3",
+                NotificationManager.IMPORTANCE_HIGH);
+
+        ParceledListSlice<NotificationChannel> pls0 =
+                new ParceledListSlice(ImmutableList.of(nc0));
+        ParceledListSlice<NotificationChannel> pls1 =
+                new ParceledListSlice(ImmutableList.of(nc1));
+        ParceledListSlice<NotificationChannel> pls2 =
+                new ParceledListSlice(ImmutableList.of(nc2, nc3));
+
+        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg0", mUid))
+                .thenReturn(pls0);
+        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg1", mUid))
+                .thenReturn(pls1);
+        when(mPreferencesHelper.getNotificationChannelsBypassingDnd("pkg2", mUid))
+                .thenReturn(pls2);
+
+        List<String> result = mBinderService.getPackagesBypassingDnd(mUserId, true);
+
+        assertThat(result).containsExactly("pkg0", "pkg1", "pkg2");
+    }
+
+    @Test
     public void testMatchesCallFilter_noPermissionShouldThrow() throws Exception {
         // set the testable NMS to not system uid/appid
         mService.isSystemUid = false;