Update channelBypassingDnd on user unlock + switch

Also add method that returns the number of apps
that are bypassing dnd

Test: atest PreferencesHelperTest
Fixes: 115972200
Bug: 111475013
Change-Id: Id75093f9f42d00d05cca7700f64493d702c6a518
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 8fce5e3..fd65ebe 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -111,7 +111,6 @@
     // pkg => PackagePreferences
     private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
 
-
     private final Context mContext;
     private final PackageManager mPm;
     private final RankingHandler mRankingHandler;
@@ -120,7 +119,6 @@
     private SparseBooleanArray mBadgingEnabled;
     private boolean mAreChannelsBypassingDnd;
 
-
     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
             ZenModeHelper zenHelper) {
         mContext = context;
@@ -129,11 +127,7 @@
         mPm = pm;
 
         updateBadgingEnabled();
-
-        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
-                NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
-        updateChannelsBypassingDnd();
-
+        syncChannelsBypassingDnd(mContext.getUserId());
     }
 
     public void readXml(XmlPullParser parser, boolean forRestore)
@@ -525,6 +519,7 @@
                 // but the system can
                 if (group.isBlocked() != oldGroup.isBlocked()) {
                     group.lockFields(NotificationChannelGroup.USER_LOCKED_BLOCKED_STATE);
+                    updateChannelsBypassingDnd(mContext.getUserId());
                 }
                 if (group.canOverlayApps() != oldGroup.canOverlayApps()) {
                     group.lockFields(NotificationChannelGroup.USER_LOCKED_ALLOW_APP_OVERLAY);
@@ -571,6 +566,7 @@
 
             // Apps are allowed to downgrade channel importance if the user has not changed any
             // fields on this channel yet.
+            final int previousExistingImportance = existing.getImportance();
             if (existing.getUserLockedFields() == 0 &&
                     channel.getImportance() < existing.getImportance()) {
                 existing.setImportance(channel.getImportance());
@@ -582,8 +578,9 @@
                 boolean bypassDnd = channel.canBypassDnd();
                 existing.setBypassDnd(bypassDnd);
 
-                if (bypassDnd != mAreChannelsBypassingDnd) {
-                    updateChannelsBypassingDnd();
+                if (bypassDnd != mAreChannelsBypassingDnd
+                        || previousExistingImportance != existing.getImportance()) {
+                    updateChannelsBypassingDnd(mContext.getUserId());
                 }
             }
 
@@ -613,7 +610,7 @@
 
         r.channels.put(channel.getId(), channel);
         if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd();
+            updateChannelsBypassingDnd(mContext.getUserId());
         }
         MetricsLogger.action(getChannelLog(channel, pkg).setType(
                 com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
@@ -663,8 +660,9 @@
             MetricsLogger.action(getChannelLog(updatedChannel, pkg));
         }
 
-        if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
-            updateChannelsBypassingDnd();
+        if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd
+                || channel.getImportance() != updatedChannel.getImportance()) {
+            updateChannelsBypassingDnd(mContext.getUserId());
         }
         updateConfig();
     }
@@ -701,7 +699,7 @@
             MetricsLogger.action(lm);
 
             if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
-                updateChannelsBypassingDnd();
+                updateChannelsBypassingDnd(mContext.getUserId());
             }
         }
     }
@@ -859,6 +857,27 @@
     }
 
     /**
+     * Gets all notification channels associated with the given pkg and userId that can bypass dnd
+     */
+    public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg,
+            int userId) {
+        List<NotificationChannel> channels = new ArrayList<>();
+        synchronized (mPackagePreferences) {
+            final PackagePreferences r = mPackagePreferences.get(
+                    packagePreferencesKey(pkg, userId));
+            // notifications from this package aren't blocked
+            if (r != null && r.importance != IMPORTANCE_NONE) {
+                for (NotificationChannel channel : r.channels.values()) {
+                    if (channelIsLive(r, channel) && channel.canBypassDnd()) {
+                        channels.add(channel);
+                    }
+                }
+            }
+        }
+        return new ParceledListSlice<>(channels);
+    }
+
+    /**
      * True for pre-O apps that only have the default channel, or pre O apps that have no
      * channels yet. This method will create the default channel for pre-O apps that don't have it.
      * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
@@ -922,18 +941,62 @@
         return count;
     }
 
-    public void updateChannelsBypassingDnd() {
+    /**
+     * Returns the number of apps that have at least one notification channel that can bypass DND
+     * for given particular user
+     */
+    public int getAppsBypassingDndCount(int userId) {
+        int count = 0;
         synchronized (mPackagePreferences) {
-            final int numPackagePreferencess = mPackagePreferences.size();
-            for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
-                    PackagePreferencesIndex++) {
-                final PackagePreferences r = mPackagePreferences.valueAt(PackagePreferencesIndex);
-                final int numChannels = r.channels.size();
+            final int numPackagePreferences = mPackagePreferences.size();
+            for (int i = 0; i < numPackagePreferences; i++) {
+                final PackagePreferences r = mPackagePreferences.valueAt(i);
+                // Package isn't associated with this userId or notifications from this package are
+                // blocked
+                if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+                    continue;
+                }
 
-                for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
-                    NotificationChannel channel = r.channels.valueAt(channelIndex);
-                    if (!channel.isDeleted() && channel.canBypassDnd()) {
-                        // If any channel bypasses DND, synchronize state and return early.
+                for (NotificationChannel channel : r.channels.values()) {
+                    if (channelIsLive(r, channel) && channel.canBypassDnd()) {
+                        count++;
+                        break;
+                    }
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Syncs {@link #mAreChannelsBypassingDnd} with the user's notification policy before
+     * updating
+     * @param userId
+     */
+    private void syncChannelsBypassingDnd(int userId) {
+        mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state
+                & NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+        updateChannelsBypassingDnd(userId);
+    }
+
+    /**
+     * Updates the user's NotificationPolicy based on whether the given userId
+     * has channels bypassing DND
+     * @param userId
+     */
+    private void updateChannelsBypassingDnd(int userId) {
+        synchronized (mPackagePreferences) {
+            final int numPackagePreferences = mPackagePreferences.size();
+            for (int i = 0; i < numPackagePreferences; i++) {
+                final PackagePreferences r = mPackagePreferences.valueAt(i);
+                // Package isn't associated with this userId or notifications from this package are
+                // blocked
+                if (userId != UserHandle.getUserId(r.uid) || r.importance == IMPORTANCE_NONE) {
+                    continue;
+                }
+
+                for (NotificationChannel channel : r.channels.values()) {
+                    if (channelIsLive(r, channel) && channel.canBypassDnd()) {
                         if (!mAreChannelsBypassingDnd) {
                             mAreChannelsBypassingDnd = true;
                             updateZenPolicy(true);
@@ -943,7 +1006,6 @@
                 }
             }
         }
-
         // If no channels bypass DND, update the zen policy once to disable DND bypass.
         if (mAreChannelsBypassingDnd) {
             mAreChannelsBypassingDnd = false;
@@ -951,6 +1013,22 @@
         }
     }
 
+    private boolean channelIsLive(PackagePreferences pkgPref, NotificationChannel channel) {
+        // Channel is in a group that's blocked
+        if (!TextUtils.isEmpty(channel.getGroup())) {
+            if (pkgPref.groups.get(channel.getGroup()).isBlocked()) {
+                return false;
+            }
+        }
+
+        // Channel is deleted or is blocked
+        if (channel.isDeleted() || channel.getImportance() == IMPORTANCE_NONE) {
+            return false;
+        }
+
+        return true;
+    }
+
     public void updateZenPolicy(boolean areChannelsBypassingDnd) {
         NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
         mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
@@ -1329,6 +1407,20 @@
         return packageChannels;
     }
 
+    /**
+     * Called when user switches
+     */
+    public void onUserSwitched(int userId) {
+        syncChannelsBypassingDnd(userId);
+    }
+
+    /**
+     * Called when user is unlocked
+     */
+    public void onUserUnlocked(int userId) {
+        syncChannelsBypassingDnd(userId);
+    }
+
     public void onUserRemoved(int userId) {
         synchronized (mPackagePreferences) {
             int N = mPackagePreferences.size();