Add more functionality to channel groups
Description, blocking, links to/from the app
Test: cts, runtest systemui-notification
Bug: 63927402
Change-Id: Icc8caf319651f9ac2d622fb54110270c89bdff61
diff --git a/api/current.txt b/api/current.txt
index d94614b..53e6a94 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5161,6 +5161,7 @@
field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
@@ -5576,8 +5577,11 @@
method public android.app.NotificationChannelGroup clone();
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
+ method public java.lang.String getDescription();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public boolean isBlocked();
+ method public void setDescription(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
@@ -34883,6 +34887,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
@@ -34942,6 +34947,7 @@
field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
diff --git a/api/system-current.txt b/api/system-current.txt
index a43fe2e..26f923d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5346,6 +5346,7 @@
field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
@@ -5782,8 +5783,11 @@
method public android.app.NotificationChannelGroup clone();
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
+ method public java.lang.String getDescription();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public boolean isBlocked();
+ method public void setDescription(java.lang.String);
method public org.json.JSONObject toJson() throws org.json.JSONException;
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
@@ -37935,6 +37939,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
@@ -37995,6 +38000,7 @@
field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
diff --git a/api/test-current.txt b/api/test-current.txt
index 87eb171..e0989e8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5174,6 +5174,7 @@
field public static final java.lang.String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents";
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown";
field public static final java.lang.String EXTRA_COLORIZED = "android.colorized";
@@ -5589,8 +5590,12 @@
method public android.app.NotificationChannelGroup clone();
method public int describeContents();
method public java.util.List<android.app.NotificationChannel> getChannels();
+ method public java.lang.String getDescription();
method public java.lang.String getId();
method public java.lang.CharSequence getName();
+ method public boolean isBlocked();
+ method public void setBlocked(boolean);
+ method public void setDescription(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR;
}
@@ -35060,6 +35065,7 @@
field public static final java.lang.String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS";
field public static final java.lang.String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
field public static final java.lang.String ACTION_CAST_SETTINGS = "android.settings.CAST_SETTINGS";
+ field public static final java.lang.String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_CHANNEL_NOTIFICATION_SETTINGS = "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
field public static final java.lang.String ACTION_DATA_ROAMING_SETTINGS = "android.settings.DATA_ROAMING_SETTINGS";
field public static final java.lang.String ACTION_DATE_SETTINGS = "android.settings.DATE_SETTINGS";
@@ -35120,6 +35126,7 @@
field public static final java.lang.String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final java.lang.String EXTRA_AUTHORITIES = "authorities";
field public static final java.lang.String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
+ field public static final java.lang.String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID";
field public static final java.lang.String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_ENABLED = "android.settings.extra.do_not_disturb_mode_enabled";
field public static final java.lang.String EXTRA_DO_NOT_DISTURB_MODE_MINUTES = "android.settings.extra.do_not_disturb_mode_minutes";
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 08821be..d4752a7 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -61,6 +61,8 @@
void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList);
ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted);
NotificationChannelGroup getNotificationChannelGroupForPackage(String groupId, String pkg, int uid);
+ NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(String pkg, int uid, String groupId, boolean includeDeleted);
+ void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String pkg, String channelId);
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
@@ -103,6 +105,7 @@
void setOnNotificationPostedTrimFromListener(in INotificationListener token, int trim);
void setInterruptionFilter(String pkg, int interruptionFilter);
+ void updateNotificationChannelGroupFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannelGroup group);
void updateNotificationChannelFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user, in NotificationChannel channel);
ParceledListSlice getNotificationChannelsFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user);
ParceledListSlice getNotificationChannelGroupsFromPrivilegedListener(in INotificationListener token, String pkg, in UserHandle user);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9511f3f..841b961 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -124,6 +124,13 @@
/**
* Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
+ * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down
+ * what settings should be shown in the target app.
+ */
+ public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID";
+
+ /**
+ * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will
* contain the tag provided to {@link NotificationManager#notify(String, int, Notification)}
* that can be used to narrow down what settings should be shown in the target app.
*/
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d6e3691..163a8dc 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -830,24 +830,24 @@
@Override
public String toString() {
- return "NotificationChannel{" +
- "mId='" + mId + '\'' +
- ", mName=" + mName +
- ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") +
- ", mImportance=" + mImportance +
- ", mBypassDnd=" + mBypassDnd +
- ", mLockscreenVisibility=" + mLockscreenVisibility +
- ", mSound=" + mSound +
- ", mLights=" + mLights +
- ", mLightColor=" + mLightColor +
- ", mVibration=" + Arrays.toString(mVibration) +
- ", mUserLockedFields=" + mUserLockedFields +
- ", mVibrationEnabled=" + mVibrationEnabled +
- ", mShowBadge=" + mShowBadge +
- ", mDeleted=" + mDeleted +
- ", mGroup='" + mGroup + '\'' +
- ", mAudioAttributes=" + mAudioAttributes +
- ", mBlockableSystem=" + mBlockableSystem +
- '}';
+ return "NotificationChannel{"
+ + "mId='" + mId + '\''
+ + ", mName=" + mName
+ + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+ + ", mImportance=" + mImportance
+ + ", mBypassDnd=" + mBypassDnd
+ + ", mLockscreenVisibility=" + mLockscreenVisibility
+ + ", mSound=" + mSound
+ + ", mLights=" + mLights
+ + ", mLightColor=" + mLightColor
+ + ", mVibration=" + Arrays.toString(mVibration)
+ + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
+ + ", mVibrationEnabled=" + mVibrationEnabled
+ + ", mShowBadge=" + mShowBadge
+ + ", mDeleted=" + mDeleted
+ + ", mGroup='" + mGroup + '\''
+ + ", mAudioAttributes=" + mAudioAttributes
+ + ", mBlockableSystem=" + mBlockableSystem
+ + '}';
}
}
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 18ad9cf..5173311 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -16,6 +16,7 @@
package android.app;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +24,7 @@
import org.json.JSONException;
import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
@@ -42,10 +44,14 @@
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_NAME = "name";
+ private static final String ATT_DESC = "desc";
private static final String ATT_ID = "id";
+ private static final String ATT_BLOCKED = "blocked";
private final String mId;
private CharSequence mName;
+ private String mDescription;
+ private boolean mBlocked;
private List<NotificationChannel> mChannels = new ArrayList<>();
/**
@@ -73,7 +79,13 @@
mId = null;
}
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ if (in.readByte() != 0) {
+ mDescription = in.readString();
+ } else {
+ mDescription = null;
+ }
in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
+ mBlocked = in.readBoolean();
}
private String getTrimmedString(String input) {
@@ -92,24 +104,38 @@
dest.writeByte((byte) 0);
}
TextUtils.writeToParcel(mName, dest, flags);
+ if (mDescription != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mDescription);
+ } else {
+ dest.writeByte((byte) 0);
+ }
dest.writeParcelableList(mChannels, flags);
+ dest.writeBoolean(mBlocked);
}
/**
- * Returns the id of this channel.
+ * Returns the id of this group.
*/
public String getId() {
return mId;
}
/**
- * Returns the user visible name of this channel.
+ * Returns the user visible name of this group.
*/
public CharSequence getName() {
return mName;
}
/**
+ * Returns the user visible description of this group.
+ */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /**
* Returns the list of channels that belong to this group
*/
public List<NotificationChannel> getChannels() {
@@ -117,6 +143,32 @@
}
/**
+ * Returns whether or not notifications posted to {@link NotificationChannel channels} belonging
+ * to this group are blocked.
+ */
+ public boolean isBlocked() {
+ return mBlocked;
+ }
+
+ /**
+ * Sets the user visible description of this group.
+ *
+ * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
+ * long.
+ */
+ public void setDescription(String description) {
+ mDescription = getTrimmedString(description);
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ public void setBlocked(boolean blocked) {
+ mBlocked = blocked;
+ }
+
+ /**
* @hide
*/
public void addChannel(NotificationChannel channel) {
@@ -126,6 +178,28 @@
/**
* @hide
*/
+ public void setChannels(List<NotificationChannel> channels) {
+ mChannels = channels;
+ }
+
+ /**
+ * @hide
+ */
+ public void populateFromXml(XmlPullParser parser) {
+ // Name, id, and importance are set in the constructor.
+ setDescription(parser.getAttributeValue(null, ATT_DESC));
+ setBlocked(safeBool(parser, ATT_BLOCKED, false));
+ }
+
+ private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+ final String value = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(value)) return defValue;
+ return Boolean.parseBoolean(value);
+ }
+
+ /**
+ * @hide
+ */
public void writeXml(XmlSerializer out) throws IOException {
out.startTag(null, TAG_GROUP);
@@ -133,6 +207,10 @@
if (getName() != null) {
out.attribute(null, ATT_NAME, getName().toString());
}
+ if (getDescription() != null) {
+ out.attribute(null, ATT_DESC, getDescription().toString());
+ }
+ out.attribute(null, ATT_BLOCKED, Boolean.toString(isBlocked()));
out.endTag(null, TAG_GROUP);
}
@@ -145,6 +223,8 @@
JSONObject record = new JSONObject();
record.put(ATT_ID, getId());
record.put(ATT_NAME, getName());
+ record.put(ATT_DESC, getDescription());
+ record.put(ATT_BLOCKED, isBlocked());
return record;
}
@@ -173,31 +253,46 @@
NotificationChannelGroup that = (NotificationChannelGroup) o;
+ if (isBlocked() != that.isBlocked()) return false;
if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
return false;
}
- return true;
- }
-
- @Override
- public NotificationChannelGroup clone() {
- return new NotificationChannelGroup(getId(), getName());
+ if (getDescription() != null ? !getDescription().equals(that.getDescription())
+ : that.getDescription() != null) {
+ return false;
+ }
+ return getChannels() != null ? getChannels().equals(that.getChannels())
+ : that.getChannels() == null;
}
@Override
public int hashCode() {
int result = getId() != null ? getId().hashCode() : 0;
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
+ result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0);
+ result = 31 * result + (isBlocked() ? 1 : 0);
+ result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0);
return result;
}
@Override
+ public NotificationChannelGroup clone() {
+ NotificationChannelGroup cloned = new NotificationChannelGroup(getId(), getName());
+ cloned.setDescription(getDescription());
+ cloned.setBlocked(isBlocked());
+ cloned.setChannels(getChannels());
+ return cloned;
+ }
+
+ @Override
public String toString() {
- return "NotificationChannelGroup{" +
- "mId='" + mId + '\'' +
- ", mName=" + mName +
- ", mChannels=" + mChannels +
- '}';
+ return "NotificationChannelGroup{"
+ + "mId='" + mId + '\''
+ + ", mName=" + mName
+ + ", mDescription=" + (!TextUtils.isEmpty(mDescription) ? "hasDescription " : "")
+ + ", mBlocked=" + mBlocked
+ + ", mChannels=" + mChannels
+ + '}';
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 34343e9..8fa7d6c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -421,7 +421,7 @@
* Creates a notification channel that notifications can be posted to.
*
* This can also be used to restore a deleted channel and to update an existing channel's
- * name, description, and/or importance.
+ * name, description, group, and/or importance.
*
* <p>The name and description should only be changed if the locale changes
* or in response to the user renaming this channel. For example, if a user has a channel
@@ -431,6 +431,9 @@
* <p>The importance of an existing channel will only be changed if the new importance is lower
* than the current value and the user has not altered any settings on this channel.
*
+ * <p>The group an existing channel will only be changed if the channel does not already
+ * belong to a group.
+ *
* All other fields are ignored for channels that already exist.
*
* @param channel the channel to create. Note that the created channel may differ from this
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 0ff4adc..872ac63 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -31,6 +31,7 @@
import android.app.AppOpsManager;
import android.app.Application;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.SearchManager;
import android.app.WallpaperManager;
@@ -1311,6 +1312,18 @@
= "android.settings.CHANNEL_NOTIFICATION_SETTINGS";
/**
+ * Activity Action: Show notification settings for a single {@link NotificationChannelGroup}.
+ * <p>
+ * Input: {@link #EXTRA_APP_PACKAGE}, the package containing the channel group to display.
+ * Input: {@link #EXTRA_CHANNEL_GROUP_ID}, the id of the channel group to display.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS =
+ "android.settings.CHANNEL_GROUP_NOTIFICATION_SETTINGS";
+
+ /**
* Activity Extra: The package owner of the notification channel settings to display.
* <p>
* This must be passed as an extra field to the {@link #ACTION_CHANNEL_NOTIFICATION_SETTINGS}.
@@ -1326,6 +1339,15 @@
public static final String EXTRA_CHANNEL_ID = "android.provider.extra.CHANNEL_ID";
/**
+ * Activity Extra: The {@link NotificationChannelGroup#getId()} of the notification channel
+ * group settings to display.
+ * <p>
+ * This must be passed as an extra field to the
+ * {@link #ACTION_CHANNEL_GROUP_NOTIFICATION_SETTINGS}.
+ */
+ public static final String EXTRA_CHANNEL_GROUP_ID = "android.provider.extra.CHANNEL_GROUP_ID";
+
+ /**
* Activity Action: Show notification redaction settings.
*
* @hide
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 3bc5923..a79f244 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -1516,7 +1516,7 @@
// OS: N
ACTION_ZEN_ALLOW_LIGHTS = 264;
- // OPEN: Settings > Notifications > [App] > Topic Notifications
+ // OPEN: Settings > Notifications > [App] > Channel Notifications
// CATEGORY: SETTINGS
// OS: N
NOTIFICATION_TOPIC_NOTIFICATION = 265;
@@ -4354,6 +4354,11 @@
// CATEGORY: SETTINGS
SETTINGS_FEATURE_FLAGS_DASHBOARD = 1156;
+ // OPEN: Settings > Notifications > [App] > Topic Notifications
+ // CATEGORY: SETTINGS
+ // OS: P
+ NOTIFICATION_CHANNEL_GROUP = 1157;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a693e97..7dbacdc 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1470,6 +1470,19 @@
savePolicyFile();
}
+ private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+ boolean fromApp, boolean fromListener) {
+ Preconditions.checkNotNull(group);
+ Preconditions.checkNotNull(pkg);
+ mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
+ fromApp);
+ if (!fromListener) {
+ mListeners.notifyNotificationChannelGroupChanged(pkg,
+ UserHandle.of(UserHandle.getCallingUserId()), group,
+ NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+ }
+ }
+
private ArrayList<ComponentName> getSuppressors() {
ArrayList<ComponentName> names = new ArrayList<ComponentName>();
for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1757,6 +1770,14 @@
}
@Override
+ public void updateNotificationChannelGroupForPackage(String pkg, int uid,
+ NotificationChannelGroup group) throws RemoteException {
+ enforceSystemOrSystemUI("Caller not system or systemui");
+ createNotificationChannelGroup(pkg, uid, group, false, false);
+ savePolicyFile();
+ }
+
+ @Override
public void createNotificationChannelGroups(String pkg,
ParceledListSlice channelGroupList) throws RemoteException {
checkCallerIsSystemOrSameApp(pkg);
@@ -1764,12 +1785,7 @@
final int groupSize = groups.size();
for (int i = 0; i < groupSize; i++) {
final NotificationChannelGroup group = groups.get(i);
- Preconditions.checkNotNull(group, "group in list is null");
- mRankingHelper.createNotificationChannelGroup(pkg, Binder.getCallingUid(), group,
- true /* fromTargetApp */);
- mListeners.notifyNotificationChannelGroupChanged(pkg,
- UserHandle.of(UserHandle.getCallingUserId()), group,
- NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
+ createNotificationChannelGroup(pkg, Binder.getCallingUid(), group, true, false);
}
savePolicyFile();
}
@@ -1915,6 +1931,14 @@
}
@Override
+ public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(
+ String pkg, int uid, String groupId, boolean includeDeleted) {
+ enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage");
+ return mRankingHelper.getNotificationChannelGroupWithChannels(
+ pkg, uid, groupId, includeDeleted);
+ }
+
+ @Override
public NotificationChannelGroup getNotificationChannelGroupForPackage(
String groupId, String pkg, int uid) {
enforceSystemOrSystemUI("getNotificationChannelGroupForPackage");
@@ -2917,6 +2941,17 @@
}
@Override
+ public void updateNotificationChannelGroupFromPrivilegedListener(
+ INotificationListener token, String pkg, UserHandle user,
+ NotificationChannelGroup group) throws RemoteException {
+ Preconditions.checkNotNull(user);
+ verifyPrivilegedListener(token, user);
+ createNotificationChannelGroup(
+ pkg, getUidForPackageAndUser(pkg, user), group, false, true);
+ savePolicyFile();
+ }
+
+ @Override
public void updateNotificationChannelFromPrivilegedListener(INotificationListener token,
String pkg, UserHandle user, NotificationChannel channel) throws RemoteException {
Preconditions.checkNotNull(channel);
@@ -3612,9 +3647,10 @@
usageStats.registerSuspendedByAdmin(r);
return isPackageSuspended;
}
-
final boolean isBlocked =
- mRankingHelper.getImportance(pkg, callingUid) == NotificationManager.IMPORTANCE_NONE
+ mRankingHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
+ || mRankingHelper.getImportance(pkg, callingUid)
+ == NotificationManager.IMPORTANCE_NONE
|| r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
if (isBlocked) {
Slog.e(TAG, "Suppressing notification from package by user request.");
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 36da04d..b5ef1c6 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -29,6 +29,7 @@
void setShowBadge(String packageName, int uid, boolean showBadge);
boolean canShowBadge(String packageName, int uid);
boolean badgingEnabled(UserHandle userHandle);
+ boolean isGroupBlocked(String packageName, int uid, String groupId);
Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
int uid);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 3386fe83..1736a74 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -215,6 +215,7 @@
if (!TextUtils.isEmpty(id)) {
NotificationChannelGroup group
= new NotificationChannelGroup(id, groupName);
+ group.populateFromXml(parser);
r.groups.put(id, group);
}
}
@@ -493,6 +494,19 @@
updateConfig();
}
+ @Override
+ public boolean isGroupBlocked(String packageName, int uid, String groupId) {
+ if (groupId == null) {
+ return false;
+ }
+ Record r = getOrCreateRecord(packageName, uid);
+ NotificationChannelGroup group = r.groups.get(groupId);
+ if (group == null) {
+ return false;
+ }
+ return group.isBlocked();
+ }
+
int getPackagePriority(String pkg, int uid) {
return getOrCreateRecord(pkg, uid).priority;
}
@@ -514,9 +528,16 @@
}
final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
if (!group.equals(oldGroup)) {
- // will log for new entries as well as name changes
+ // will log for new entries as well as name/description changes
MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
}
+ if (oldGroup != null) {
+ group.setChannels(oldGroup.getChannels());
+
+ if (fromTargetApp) {
+ group.setBlocked(oldGroup.isBlocked());
+ }
+ }
r.groups.put(group.getId(), group);
}
@@ -552,6 +573,9 @@
existing.setName(channel.getName().toString());
existing.setDescription(channel.getDescription());
existing.setBlockableSystem(channel.isBlockableSystem());
+ if (existing.getGroup() == null) {
+ existing.setGroup(channel.getGroup());
+ }
// Apps are allowed to downgrade channel importance if the user has not changed any
// fields on this channel yet.
@@ -684,6 +708,27 @@
}
}
+ public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
+ int uid, String groupId, boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ Record r = getRecord(pkg, uid);
+ if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
+ return null;
+ }
+ NotificationChannelGroup group = r.groups.get(groupId).clone();
+ group.setChannels(new ArrayList<>());
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ if (groupId.equals(nc.getGroup())) {
+ group.addChannel(nc);
+ }
+ }
+ }
+ return group;
+ }
+
public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
int uid) {
Preconditions.checkNotNull(pkg);
@@ -710,6 +755,7 @@
NotificationChannelGroup ncg = groups.get(nc.getGroup());
if (ncg == null) {
ncg = r.groups.get(nc.getGroup()).clone();
+ ncg.setChannels(new ArrayList<>());
groups.put(nc.getGroup(), ncg);
}
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index 04b42f1..ddd21df 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -429,6 +429,20 @@
}
@Test
+ public void testBlockedNotifications_blockedChannelGroup() throws Exception {
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ mNotificationManagerService.setRankingHelper(mRankingHelper);
+ when(mRankingHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
+
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ channel.setGroup("something");
+ NotificationRecord r = generateNotificationRecord(channel);
+ assertTrue(mNotificationManagerService.isBlocked(r, mUsageStats));
+ verify(mUsageStats, times(1)).registerBlocked(eq(r));
+ }
+
+ @Test
public void testEnqueuedBlockedNotifications_blockedApp() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 65bf330..306dd98 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -240,12 +240,23 @@
private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
+ assertEquals(expected.isBlocked(), actual.isBlocked());
}
private NotificationChannel getChannel() {
return new NotificationChannel("id", "name", IMPORTANCE_LOW);
}
+ private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
+ for (NotificationChannel channel : channels) {
+ if (channel.getId().equals(id)) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
@Test
public void testFindAfterRankingWithASplitGroup() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -299,6 +310,8 @@
@Test
public void testChannelXml() throws Exception {
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+ ncg.setBlocked(true);
+ ncg.setDescription("group desc");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
@@ -1294,6 +1307,26 @@
}
@Test
+ public void testCreateChannel_addToGroup() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
+ NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertNull(actual.getGroup());
+
+ nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
+ nc.setGroup(group.getId());
+ mHelper.createNotificationChannel(PKG, UID, nc, true);
+
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertNotNull(actual.getGroup());
+ assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+
+ verify(mHandler, times(1)).requestSort();
+ }
+
+ @Test
public void testDumpChannelsJson() throws Exception {
final ApplicationInfo upgrade = new ApplicationInfo();
upgrade.targetSdkVersion = Build.VERSION_CODES.O;
@@ -1388,4 +1421,77 @@
assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID,
NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
}
+
+ @Test
+ public void testIsGroupBlocked_noGroup() throws Exception {
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, null));
+
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group"));
+ }
+
+ @Test
+ public void testIsGroupBlocked_notBlocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testIsGroupBlocked_blocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ group.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG, UID, group, false);
+
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testIsGroup_appCannotResetBlock() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ NotificationChannelGroup group2 = group.clone();
+ group2.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG, UID, group2, false);
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+
+ NotificationChannelGroup group3 = group.clone();
+ group3.setBlocked(false);
+ mHelper.createNotificationChannelGroup(PKG, UID, group3, true);
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testGetNotificationChannelGroupWithChannels() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, other, true);
+
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+ a.setGroup(group.getId());
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
+ b.setGroup(other.getId());
+ NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+ c.setGroup(group.getId());
+ NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);
+
+ mHelper.createNotificationChannel(PKG, UID, a, true);
+ mHelper.createNotificationChannel(PKG, UID, b, true);
+ mHelper.createNotificationChannel(PKG, UID, c, true);
+ mHelper.createNotificationChannel(PKG, UID, d, true);
+ mHelper.deleteNotificationChannel(PKG, UID, c.getId());
+
+ NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
+ PKG, UID, group.getId(), true);
+ assertEquals(2, retrieved.getChannels().size());
+ compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+ compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));
+
+ retrieved = mHelper.getNotificationChannelGroupWithChannels(
+ PKG, UID, group.getId(), false);
+ assertEquals(1, retrieved.getChannels().size());
+ compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+ }
}