diff options
7 files changed, 196 insertions, 21 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 0f2a11a496bf..cd127102f83b 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -87,6 +87,7 @@ interface INotificationManager boolean onlyHasDefaultChannel(String pkg, int uid); ParceledListSlice getRecentNotifyingAppsForUser(int userId); int getBlockedAppCount(int userId); + boolean areChannelsBypassingDnd(); // TODO: Remove this when callers have been migrated to the equivalent // INotificationListener method. diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index a77a011675c9..f6dc5d15f385 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1168,6 +1168,23 @@ public class NotificationManager { public final int suppressedVisualEffects; /** + * @hide + */ + public static final int STATE_CHANNELS_BYPASSING_DND = 1 << 0; + + /** + * @hide + */ + public static final int STATE_UNSET = -1; + + /** + * Notification state information that is necessary to determine Do Not Disturb behavior. + * Bitmask of STATE_* constants. + * @hide + */ + public final int state; + + /** * Constructs a policy for Do Not Disturb priority mode behavior. * * <p> @@ -1182,7 +1199,7 @@ public class NotificationManager { */ public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders) { this(priorityCategories, priorityCallSenders, priorityMessageSenders, - SUPPRESSED_EFFECTS_UNSET); + SUPPRESSED_EFFECTS_UNSET, STATE_UNSET); } /** @@ -1220,11 +1237,23 @@ public class NotificationManager { this.priorityCallSenders = priorityCallSenders; this.priorityMessageSenders = priorityMessageSenders; this.suppressedVisualEffects = suppressedVisualEffects; + this.state = STATE_UNSET; + } + + /** @hide */ + public Policy(int priorityCategories, int priorityCallSenders, int priorityMessageSenders, + int suppressedVisualEffects, int state) { + this.priorityCategories = priorityCategories; + this.priorityCallSenders = priorityCallSenders; + this.priorityMessageSenders = priorityMessageSenders; + this.suppressedVisualEffects = suppressedVisualEffects; + this.state = state; } /** @hide */ public Policy(Parcel source) { - this(source.readInt(), source.readInt(), source.readInt(), source.readInt()); + this(source.readInt(), source.readInt(), source.readInt(), source.readInt(), + source.readInt()); } @Override @@ -1233,6 +1262,7 @@ public class NotificationManager { dest.writeInt(priorityCallSenders); dest.writeInt(priorityMessageSenders); dest.writeInt(suppressedVisualEffects); + dest.writeInt(state); } @Override @@ -1265,6 +1295,8 @@ public class NotificationManager { + ",priorityMessageSenders=" + prioritySendersToString(priorityMessageSenders) + ",suppressedVisualEffects=" + suppressedEffectsToString(suppressedVisualEffects) + + ",areChannelsBypassingDnd=" + (((state & STATE_CHANNELS_BYPASSING_DND) != 0) + ? "true" : "false") + "]"; } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index e3f4ad1887ae..309fa4afbd54 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -95,6 +95,7 @@ public class ZenModeConfig implements Parcelable { private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false; private static final boolean DEFAULT_ALLOW_SCREEN_OFF = false; private static final boolean DEFAULT_ALLOW_SCREEN_ON = false; + private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false; private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = Policy.getAllSuppressedVisualEffects(); @@ -118,6 +119,8 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn"; private static final String DISALLOW_TAG = "disallow"; private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects"; + private static final String STATE_TAG = "state"; + private static final String STATE_ATT_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd"; private static final String CONDITION_ATT_ID = "id"; private static final String CONDITION_ATT_SUMMARY = "summary"; @@ -154,6 +157,7 @@ public class ZenModeConfig implements Parcelable { public int suppressedVisualEffects = DEFAULT_SUPPRESSED_VISUAL_EFFECTS; public boolean allowWhenScreenOff = DEFAULT_ALLOW_SCREEN_OFF; public boolean allowWhenScreenOn = DEFAULT_ALLOW_SCREEN_ON; + public boolean areChannelsBypassingDnd = DEFAULT_CHANNELS_BYPASSING_DND; public int version; public ZenRule manualRule; @@ -187,6 +191,7 @@ public class ZenModeConfig implements Parcelable { allowMedia = source.readInt() == 1; allowSystem = source.readInt() == 1; suppressedVisualEffects = source.readInt(); + areChannelsBypassingDnd = source.readInt() == 1; } @Override @@ -220,6 +225,7 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(allowMedia ? 1 : 0); dest.writeInt(allowSystem ? 1 : 0); dest.writeInt(suppressedVisualEffects); + dest.writeInt(areChannelsBypassingDnd ? 1 : 0); } @Override @@ -239,6 +245,7 @@ public class ZenModeConfig implements Parcelable { .append(",allowWhenScreenOff=").append(allowWhenScreenOff) .append(",allowWhenScreenOn=").append(allowWhenScreenOn) .append(",suppressedVisualEffects=").append(suppressedVisualEffects) + .append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd) .append(",automaticRules=").append(automaticRules) .append(",manualRule=").append(manualRule) .append(']').toString(); @@ -303,6 +310,11 @@ public class ZenModeConfig implements Parcelable { ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule); } ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule); + + if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) { + d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd, + to.areChannelsBypassingDnd); + } return d; } @@ -397,7 +409,8 @@ public class ZenModeConfig implements Parcelable { && other.user == user && Objects.equals(other.automaticRules, automaticRules) && Objects.equals(other.manualRule, manualRule) - && other.suppressedVisualEffects == suppressedVisualEffects; + && other.suppressedVisualEffects == suppressedVisualEffects + && other.areChannelsBypassingDnd == areChannelsBypassingDnd; } @Override @@ -406,7 +419,7 @@ public class ZenModeConfig implements Parcelable { allowRepeatCallers, allowMessages, allowCallsFrom, allowMessagesFrom, allowReminders, allowEvents, allowWhenScreenOff, allowWhenScreenOn, user, automaticRules, manualRule, - suppressedVisualEffects); + suppressedVisualEffects, areChannelsBypassingDnd); } private static String toDayList(int[] days) { @@ -511,6 +524,9 @@ public class ZenModeConfig implements Parcelable { automaticRule.id = id; rt.automaticRules.put(id, automaticRule); } + } else if (STATE_TAG.equals(tag)) { + rt.areChannelsBypassingDnd = safeBoolean(parser, + STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND); } } } @@ -561,6 +577,12 @@ public class ZenModeConfig implements Parcelable { writeRuleXml(automaticRule, out); out.endTag(null, AUTOMATIC_TAG); } + + out.startTag(null, STATE_TAG); + out.attribute(null, STATE_ATT_CHANNELS_BYPASSING_DND, + Boolean.toString(areChannelsBypassingDnd)); + out.endTag(null, STATE_TAG); + out.endTag(null, ZEN_TAG); } @@ -743,7 +765,8 @@ public class ZenModeConfig implements Parcelable { priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); return new Policy(priorityCategories, priorityCallSenders, priorityMessageSenders, - suppressedVisualEffects); + suppressedVisualEffects, areChannelsBypassingDnd + ? Policy.STATE_CHANNELS_BYPASSING_DND : 0); } /** @@ -795,6 +818,9 @@ public class ZenModeConfig implements Parcelable { if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) { suppressedVisualEffects = policy.suppressedVisualEffects; } + if (policy.state != Policy.STATE_UNSET) { + areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; + } } public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { @@ -1465,15 +1491,15 @@ public class ZenModeConfig implements Parcelable { & NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS) != 0; boolean allowRepeatCallers = (policy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; + boolean areChannelsBypassingDnd = (policy.state & Policy.STATE_CHANNELS_BYPASSING_DND) != 0; return !allowReminders && !allowCalls && !allowMessages && !allowEvents - && !allowRepeatCallers; + && !allowRepeatCallers && !areChannelsBypassingDnd; } /** * Determines if DND is currently overriding the ringer */ public static boolean isZenOverridingRinger(int zen, ZenModeConfig zenConfig) { - // TODO (beverlyt): check if apps can bypass dnd b/77729075 return zen == Global.ZEN_MODE_NO_INTERRUPTIONS || zen == Global.ZEN_MODE_ALARMS || (zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS @@ -1485,7 +1511,8 @@ public class ZenModeConfig implements Parcelable { */ public static boolean areAllPriorityOnlyNotificationZenSoundsMuted(ZenModeConfig config) { return !config.allowReminders && !config.allowCalls && !config.allowMessages - && !config.allowEvents && !config.allowRepeatCallers; + && !config.allowEvents && !config.allowRepeatCallers + && !config.areChannelsBypassingDnd; } /** diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml index dce8a659f983..3a7185105c48 100644 --- a/core/res/res/xml/default_zen_mode_config.xml +++ b/core/res/res/xml/default_zen_mode_config.xml @@ -19,8 +19,12 @@ <!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. --> <zen version="7"> - <allow alarms="true" media="true" system="false" calls="false" messages="false" reminders="false" - events="false" /> + <allow alarms="true" media="true" system="false" calls="false" messages="false" + reminders="false" events="false" /> + <!-- all visual effects that exist as of P --> <disallow suppressedVisualEffect="511" /> + + <!-- whether there are notification channels that can bypass dnd --> + <state areChannelsBypassingDnd="false" /> </zen> diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index afc1f54d8268..17f6d5c07652 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2400,6 +2400,11 @@ public class NotificationManagerService extends SystemService { } @Override + public boolean areChannelsBypassingDnd() { + return mRankingHelper.areChannelsBypassingDnd(); + } + + @Override public void clearData(String packageName, int uid, boolean fromApp) throws RemoteException { checkCallerIsSystem(); @@ -3281,7 +3286,6 @@ public class NotificationManagerService extends SystemService { policy = new Policy(policy.priorityCategories, policy.priorityCallSenders, policy.priorityMessageSenders, newVisualEffects); - ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy); mZenModeHelper.setNotificationPolicy(policy); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 89bd6608f53d..0c444cb82523 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -17,13 +17,6 @@ package com.android.server.notification; import static android.app.NotificationManager.IMPORTANCE_NONE; -import com.android.internal.R; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto; -import com.android.internal.util.Preconditions; -import com.android.internal.util.XmlUtils; - import android.annotation.IntDef; import android.annotation.NonNull; import android.app.Notification; @@ -52,6 +45,13 @@ import android.util.Slog; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.util.Preconditions; +import com.android.internal.util.XmlUtils; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -65,11 +65,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.concurrent.ConcurrentHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; public class RankingHelper implements RankingConfig { private static final String TAG = "RankingHelper"; @@ -127,12 +127,15 @@ public class RankingHelper implements RankingConfig { private String mPermissionControllerPackageName; private String mServicesSystemSharedLibPackageName; private String mSharedSystemSharedLibPackageName; + private boolean mAreChannelsBypassingDnd; + private ZenModeHelper mZenModeHelper; public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler, ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) { mContext = context; mRankingHandler = rankingHandler; mPm = pm; + mZenModeHelper= zenHelper; mPreliminaryComparator = new NotificationComparator(mContext); @@ -159,6 +162,7 @@ public class RankingHelper implements RankingConfig { } getSignatures(); + updateChannelsBypassingDnd(); } @SuppressWarnings("unchecked") @@ -648,7 +652,12 @@ public class RankingHelper implements RankingConfig { // system apps and dnd access apps can bypass dnd if the user hasn't changed any // fields on the channel yet if (existing.getUserLockedFields() == 0 && (isSystemApp || hasDndAccess)) { - existing.setBypassDnd(channel.canBypassDnd()); + boolean bypassDnd = channel.canBypassDnd(); + existing.setBypassDnd(bypassDnd); + + if (bypassDnd != mAreChannelsBypassingDnd) { + updateChannelsBypassingDnd(); + } } updateConfig(); @@ -675,6 +684,9 @@ public class RankingHelper implements RankingConfig { } r.channels.put(channel.getId(), channel); + if (channel.canBypassDnd() != mAreChannelsBypassingDnd) { + updateChannelsBypassingDnd(); + } MetricsLogger.action(getChannelLog(channel, pkg).setType( MetricsProto.MetricsEvent.TYPE_OPEN)); } @@ -781,6 +793,10 @@ public class RankingHelper implements RankingConfig { // only log if there are real changes MetricsLogger.action(getChannelLog(updatedChannel, pkg)); } + + if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) { + updateChannelsBypassingDnd(); + } updateConfig(); } @@ -814,6 +830,10 @@ public class RankingHelper implements RankingConfig { LogMaker lm = getChannelLog(channel, pkg); lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); MetricsLogger.action(lm); + + if (mAreChannelsBypassingDnd && channel.canBypassDnd()) { + updateChannelsBypassingDnd(); + } } } @@ -1026,6 +1046,45 @@ public class RankingHelper implements RankingConfig { return count; } + public void updateChannelsBypassingDnd() { + synchronized (mRecords) { + final int numRecords = mRecords.size(); + for (int recordIndex = 0; recordIndex < numRecords; recordIndex++) { + final Record r = mRecords.valueAt(recordIndex); + final int numChannels = r.channels.size(); + + for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) { + NotificationChannel channel = r.channels.valueAt(channelIndex); + if (!channel.isDeleted() && channel.canBypassDnd()) { + if (!mAreChannelsBypassingDnd) { + mAreChannelsBypassingDnd = true; + updateZenPolicy(true); + } + return; + } + } + } + } + + if (mAreChannelsBypassingDnd) { + mAreChannelsBypassingDnd = false; + updateZenPolicy(false); + } + } + + public void updateZenPolicy(boolean areChannelsBypassingDnd) { + NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy(); + mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy( + policy.priorityCategories, policy.priorityCallSenders, + policy.priorityMessageSenders, policy.suppressedVisualEffects, + (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND + : 0))); + } + + public boolean areChannelsBypassingDnd() { + return mAreChannelsBypassingDnd; + } + /** * Sets importance. */ diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java index bda6b8a50a3d..8183a7467277 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java @@ -172,9 +172,13 @@ public class RankingHelperTest extends UiServiceTestCase { when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI))) .thenReturn(SOUND_URI); - mHelper = new RankingHelper(getContext(), mPm, mHandler, mock(ZenModeHelper.class), + ZenModeHelper mockZenModeHelper = mock(ZenModeHelper.class); + mHelper = new RankingHelper(getContext(), mPm, mHandler, mockZenModeHelper, mUsageStats, new String[] {ImportanceExtractor.class.getName()}); + when(mockZenModeHelper.getNotificationPolicy()).thenReturn(new NotificationManager.Policy( + 0, 0, 0)); + mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setContentTitle("A") .setGroup("G") @@ -1129,6 +1133,50 @@ public class RankingHelperTest extends UiServiceTestCase { } @Test + public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception { + // create notification channel that can't bypass dnd + // expected result: areChannelsBypassingDnd = false + NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); + assertFalse(mHelper.areChannelsBypassingDnd()); + + // create notification channel that can bypass dnd + // expected result: areChannelsBypassingDnd = true + NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW); + channel2.setBypassDnd(true); + mHelper.createNotificationChannel(PKG, UID, channel2, true, true); + assertTrue(mHelper.areChannelsBypassingDnd()); + + // delete channels + mHelper.deleteNotificationChannel(PKG, UID, channel.getId()); + assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND + mHelper.deleteNotificationChannel(PKG, UID, channel2.getId()); + assertFalse(mHelper.areChannelsBypassingDnd()); + + } + + @Test + public void testUpdateCanChannelsBypassDnd() throws Exception { + // create notification channel that can't bypass dnd + // expected result: areChannelsBypassingDnd = false + NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW); + mHelper.createNotificationChannel(PKG, UID, channel, true, false); + assertFalse(mHelper.areChannelsBypassingDnd()); + + // update channel so it CAN bypass dnd: + // expected result: areChannelsBypassingDnd = true + channel.setBypassDnd(true); + mHelper.updateNotificationChannel(PKG, UID, channel, true); + assertTrue(mHelper.areChannelsBypassingDnd()); + + // update channel so it can't bypass dnd: + // expected result: areChannelsBypassingDnd = false + channel.setBypassDnd(false); + mHelper.updateNotificationChannel(PKG, UID, channel, true); + assertFalse(mHelper.areChannelsBypassingDnd()); + } + + @Test public void testCreateDeletedChannel() throws Exception { long[] vibration = new long[]{100, 67, 145, 156}; NotificationChannel channel = |