Merge "Update ZenModeAddBypassingApps to not binder call for every app" into main
diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java
index 3ce377e..31972ed 100644
--- a/src/com/android/settings/notification/NotificationBackend.java
+++ b/src/com/android/settings/notification/NotificationBackend.java
@@ -23,6 +23,7 @@
 
 import static com.android.server.notification.Flags.notificationHideUnusedChannels;
 
+import android.annotation.FlaggedApi;
 import android.app.Flags;
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
@@ -67,10 +68,13 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 
 public class NotificationBackend {
     private static final String TAG = "NotificationBackend";
@@ -368,6 +372,20 @@
         }
     }
 
+    /**
+     * Returns a set of all apps that have any notification channels (not including deleted ones).
+     */
+    @FlaggedApi(Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS)
+    public @NonNull Set<String> getPackagesWithAnyChannels(int userId) {
+        try {
+            List<String> packages = sINM.getPackagesWithAnyChannels(userId);
+            return new HashSet<>(packages);
+        } catch (Exception e) {
+            Log.w(TAG, "Error calling NoMan", e);
+            return Collections.EMPTY_SET;
+        }
+    }
+
     public void updateChannel(String pkg, int uid, NotificationChannel channel) {
         try {
             sINM.updateNotificationChannelForPackage(pkg, uid, channel);
diff --git a/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java
index ccd35ec..37f2205 100644
--- a/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceController.java
@@ -17,11 +17,13 @@
 package com.android.settings.notification.modes;
 
 import android.app.Application;
+import android.app.Flags;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -44,7 +46,11 @@
 import com.android.settingslib.widget.AppPreference;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 
 /**
@@ -58,6 +64,8 @@
     private static final String KEY = "zen_mode_non_bypassing_apps_list";
     private static final String KEY_ADD = "zen_mode_bypassing_apps_add";
     @Nullable private final NotificationBackend mNotificationBackend;
+    @Nullable private final ZenHelperBackend mHelperBackend;
+    @Nullable private final UserManager mUserManager;
 
     @Nullable @VisibleForTesting ApplicationsState mApplicationsState;
     @VisibleForTesting PreferenceScreen mPreferenceScreen;
@@ -69,18 +77,22 @@
     @Nullable private Fragment mHostFragment;
 
     public ZenModeAddBypassingAppsPreferenceController(Context context, @Nullable Application app,
-            @Nullable Fragment host, @Nullable NotificationBackend notificationBackend) {
+            @Nullable Fragment host, @Nullable NotificationBackend notificationBackend,
+            @Nullable ZenHelperBackend helperBackend) {
         this(context, app == null ? null : ApplicationsState.getInstance(app), host,
-                notificationBackend);
+                notificationBackend, helperBackend);
     }
 
     private ZenModeAddBypassingAppsPreferenceController(Context context,
             @Nullable ApplicationsState appState, @Nullable Fragment host,
-            @Nullable NotificationBackend notificationBackend) {
+            @Nullable NotificationBackend notificationBackend,
+            @Nullable ZenHelperBackend helperBackend) {
         super(context);
         mNotificationBackend = notificationBackend;
         mApplicationsState = appState;
         mHostFragment = host;
+        mHelperBackend = helperBackend;
+        mUserManager = context.getSystemService(UserManager.class);
     }
 
     @Override
@@ -147,7 +159,7 @@
 
     @VisibleForTesting
     void updateAppList(List<ApplicationsState.AppEntry> apps) {
-        if (apps == null) {
+        if (apps == null || mNotificationBackend == null) {
             return;
         }
 
@@ -157,21 +169,49 @@
             mPreferenceScreen.addPreference(mPreferenceCategory);
         }
 
+        Map<Integer, Set<String>> packagesByUser = new HashMap<>();
+        Map<Integer, Map<String, Boolean>> packagesBypassingDndByUser = new HashMap<>();
+        if (Flags.nmBinderPerfGetAppsWithChannels()) {
+            if (mHelperBackend == null || mUserManager == null) {
+                return;
+            }
+            for (UserHandle userHandle : mUserManager.getUserProfiles()) {
+                int userId = userHandle.getIdentifier();
+                packagesByUser.put(userId, mNotificationBackend.getPackagesWithAnyChannels(userId));
+                packagesBypassingDndByUser.put(userId,
+                        mHelperBackend.getPackagesBypassingDnd(userId));
+            }
+        }
         boolean doAnyAppsPassCriteria = false;
         for (ApplicationsState.AppEntry app : apps) {
             String pkg = app.info.packageName;
             final String key = getKey(pkg, app.info.uid);
-            final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
-            final int appChannelsBypassingDnd = mNotificationBackend
-                    .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
-            if (appChannelsBypassingDnd == 0 && appChannels > 0) {
+            int userId = UserHandle.getUserId(app.info.uid);
+
+            boolean doesAppBypassDnd, doesAppHaveAnyChannels;
+            if (Flags.nmBinderPerfGetAppsWithChannels()) {
+                Set<String> packagesWithChannels = packagesByUser.getOrDefault(userId,
+                        Collections.EMPTY_SET);
+                Map<String, Boolean> packagesBypassingDnd =
+                        packagesBypassingDndByUser.getOrDefault(userId, new HashMap<>());
+                doesAppBypassDnd = packagesBypassingDnd.containsKey(pkg);
+                doesAppHaveAnyChannels = packagesWithChannels.contains(pkg);
+            } else {
+                final int appChannels = mNotificationBackend.getChannelCount(pkg, app.info.uid);
+                final int appChannelsBypassingDnd = mNotificationBackend
+                        .getNotificationChannelsBypassingDnd(pkg, app.info.uid).getList().size();
+                doesAppBypassDnd = (appChannelsBypassingDnd != 0);
+                doesAppHaveAnyChannels = (appChannels > 0);
+            }
+
+            if (!doesAppBypassDnd && doesAppHaveAnyChannels) {
                 doAnyAppsPassCriteria = true;
             }
 
             Preference pref = mPreferenceCategory.findPreference(key);
 
             if (pref == null) {
-                if (appChannelsBypassingDnd == 0 && appChannels > 0) {
+                if (!doesAppBypassDnd && doesAppHaveAnyChannels) {
                     // does not exist but should
                     pref = new AppPreference(mPrefContext);
                     pref.setKey(key);
@@ -193,7 +233,7 @@
                     updateIcon(pref, app);
                     mPreferenceCategory.addPreference(pref);
                 }
-            } else if (appChannelsBypassingDnd != 0 || appChannels == 0) {
+            } else if (doesAppBypassDnd || !doesAppHaveAnyChannels) {
                 // exists but shouldn't anymore
                 mPreferenceCategory.removePreference(pref);
             }
diff --git a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
index 21f34a2..05703fb 100644
--- a/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
+++ b/src/com/android/settings/notification/modes/ZenModeSelectBypassingAppsFragment.java
@@ -60,7 +60,7 @@
         controllers.add(new ZenModeAllBypassingAppsPreferenceController(context, app, host,
                 zenHelperBackend));
         controllers.add(new ZenModeAddBypassingAppsPreferenceController(context, app, host,
-                notificationBackend));
+                notificationBackend, zenHelperBackend));
         return controllers;
     }
 
diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java
index c524ab9..e104939 100644
--- a/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModeAddBypassingAppsPreferenceControllerTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -30,6 +31,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ParceledListSlice;
 import android.platform.test.annotations.EnableFlags;
+import android.platform.test.annotations.UsesFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.fragment.app.Fragment;
@@ -46,36 +49,50 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
+import org.robolectric.ParameterizedRobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(ParameterizedRobolectricTestRunner.class)
+@UsesFlags(android.app.Flags.class)
 @EnableFlags(Flags.FLAG_MODES_UI)
 public class ZenModeAddBypassingAppsPreferenceControllerTest {
-
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     @Mock
     private NotificationBackend mBackend;
     @Mock
+    private ZenHelperBackend mHelperBackend;
+    @Mock
     private PreferenceCategory mPreferenceCategory;
     @Mock
     private ApplicationsState mApplicationState;
     private ZenModeAddBypassingAppsPreferenceController mController;
     private Context mContext;
 
+    @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return FlagsParameterization.allCombinationsOf(
+                Flags.FLAG_NM_BINDER_PERF_GET_APPS_WITH_CHANNELS);
+    }
+
+    public ZenModeAddBypassingAppsPreferenceControllerTest(FlagsParameterization flags) {
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
 
         mController = new ZenModeAddBypassingAppsPreferenceController(
-                mContext, null, mock(Fragment.class), mBackend);
+                mContext, null, mock(Fragment.class), mBackend, mHelperBackend);
         mController.mPreferenceCategory = mPreferenceCategory;
         mController.mApplicationsState = mApplicationState;
         mController.mPrefContext = mContext;
@@ -132,6 +149,12 @@
                 appWithChannelsNoneBypassing.info.uid))
                 .thenReturn(new ParceledListSlice<>(new ArrayList<>()));
 
+        // used when NM_BINDER_PERF_GET_APPS_WITH_CHANNELS flag is true
+        when(mBackend.getPackagesWithAnyChannels(anyInt())).thenReturn(
+                Set.of("appWithBypassingChannels", "appWithChannelsNoneBypassing"));
+        when(mHelperBackend.getPackagesBypassingDnd(anyInt())).thenReturn(
+                Map.of("appWithBypassingChannels", false));
+
         List<ApplicationsState.AppEntry> appEntries = new ArrayList<>();
         appEntries.add(appWithBypassingChannels);
         appEntries.add(appWithoutChannels);