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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1c7572e..7bd52bc 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1160,6 +1160,7 @@
mConditionProviders.onUserSwitched(userId);
mListeners.onUserSwitched(userId);
mZenModeHelper.onUserSwitched(userId);
+ mPreferencesHelper.onUserSwitched(userId);
}
// assistant is the only thing that cares about managed profiles specifically
mAssistants.onUserSwitched(userId);
@@ -1188,6 +1189,7 @@
mConditionProviders.onUserUnlocked(userId);
mListeners.onUserUnlocked(userId);
mZenModeHelper.onUserUnlocked(userId);
+ mPreferencesHelper.onUserUnlocked(userId);
}
}
}
@@ -2525,6 +2527,19 @@
}
@Override
+ public int getAppsBypassingDndCount(int userId) {
+ checkCallerIsSystem();
+ return mPreferencesHelper.getAppsBypassingDndCount(userId);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(
+ String pkg, int userId) {
+ checkCallerIsSystem();
+ return mPreferencesHelper.getNotificationChannelsBypassingDnd(pkg, userId);
+ }
+
+ @Override
public boolean areChannelsBypassingDnd() {
return mPreferencesHelper.areChannelsBypassingDnd();
}
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();
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 3fe381b..1a218b2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -27,7 +27,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -98,7 +97,7 @@
private static final UserHandle USER = UserHandle.of(0);
private static final int UID_O = 1111;
private static final String SYSTEM_PKG = "android";
- private static final int SYSTEM_UID= 1000;
+ private static final int SYSTEM_UID = 1000;
private static final UserHandle USER2 = UserHandle.of(10);
private static final String TEST_CHANNEL_ID = "test_channel_id";
private static final String TEST_AUTHORITY = "test";
@@ -1091,6 +1090,158 @@
}
@Test
+ public void testGetChannelsBypassingDndCount_noChannelsBypassing() throws Exception {
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ USER.getIdentifier()).getList().size());
+ }
+
+ @Test
+ public void testGetChannelsBypassingDnd_noChannelsForUserIdBypassing()
+ throws Exception {
+ int user = 9;
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_MAX);
+ channel.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);
+
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+ }
+
+ @Test
+ public void testGetChannelsBypassingDndCount_oneChannelBypassing_groupBlocked() {
+ int user = USER.getIdentifier();
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+
+ assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // disable group
+ ncg.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ false);
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+ }
+
+ @Test
+ public void testGetChannelsBypassingDndCount_multipleChannelsBypassing() {
+ int user = USER.getIdentifier();
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(true);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
+ assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // block notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, false);
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // re-enable notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, true);
+ assertEquals(3, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // setBypassDnd false for some channels
+ channel1.setBypassDnd(false);
+ channel2.setBypassDnd(false);
+ assertEquals(1, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+
+ // setBypassDnd false for rest of the channels
+ channel3.setBypassDnd(false);
+ assertEquals(0, mHelper.getNotificationChannelsBypassingDnd(PKG_N_MR1,
+ user).getList().size());
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_noAppsBypassing() throws Exception {
+ assertEquals(0, mHelper.getAppsBypassingDndCount(USER.getIdentifier()));
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_noAppsForUserIdBypassing() throws Exception {
+ int user = 9;
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_MAX);
+ channel.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG_N_MR1, 111, channel, true, true);
+
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_oneChannelBypassing_groupBlocked() {
+ int user = USER.getIdentifier();
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // disable group
+ ncg.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG_N_MR1, user, ncg, /* fromTargetApp */ false);
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+ }
+
+ @Test
+ public void testGetAppsBypassingDndCount_oneAppBypassing() {
+ int user = USER.getIdentifier();
+ NotificationChannel channel1 = new NotificationChannel("id1", "name1",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_MAX);
+ NotificationChannel channel3 = new NotificationChannel("id3", "name3",
+ NotificationManager.IMPORTANCE_MAX);
+ channel1.setBypassDnd(true);
+ channel2.setBypassDnd(true);
+ channel3.setBypassDnd(true);
+ // has DND access, so can set bypassDnd attribute
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel1, true, /*has DND access*/ true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel2, true, true);
+ mHelper.createNotificationChannel(PKG_N_MR1, user, channel3, true, true);
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // block notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, false);
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user)); // no apps can bypass dnd
+
+ // re-enable notifications from this app
+ mHelper.setEnabled(PKG_N_MR1, user, true);
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // setBypassDnd false for some channels
+ channel1.setBypassDnd(false);
+ channel2.setBypassDnd(false);
+ assertEquals(1, mHelper.getAppsBypassingDndCount(user));
+
+ // setBypassDnd false for rest of the channels
+ channel3.setBypassDnd(false);
+ assertEquals(0, mHelper.getAppsBypassingDndCount(user));
+ }
+
+ @Test
public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
// create notification channel that can't bypass dnd
// expected result: areChannelsBypassingDnd = false