Merge "Allow channels to have a user visible description."
diff --git a/api/current.txt b/api/current.txt
index 006fa51..6ecf213 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5507,6 +5507,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method public java.lang.String getDescription();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5516,6 +5517,7 @@
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
+ method public void setDescription(java.lang.String);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLightColor(int);
diff --git a/api/system-current.txt b/api/system-current.txt
index caae014..2303433 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5692,6 +5692,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method public java.lang.String getDescription();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5704,6 +5705,7 @@
method public boolean isDeleted();
method public void populateFromXml(org.xmlpull.v1.XmlPullParser);
method public void setBypassDnd(boolean);
+ method public void setDescription(java.lang.String);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLightColor(int);
@@ -5719,16 +5721,6 @@
method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException;
field public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
- field public static final int[] LOCKABLE_FIELDS;
- field public static final int USER_LOCKED_ALLOWED = 64; // 0x40
- field public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 256; // 0x100
- field public static final int USER_LOCKED_IMPORTANCE = 4; // 0x4
- field public static final int USER_LOCKED_LIGHTS = 8; // 0x8
- field public static final int USER_LOCKED_PRIORITY = 1; // 0x1
- field public static final int USER_LOCKED_SHOW_BADGE = 128; // 0x80
- field public static final int USER_LOCKED_SOUND = 32; // 0x20
- field public static final int USER_LOCKED_VIBRATION = 16; // 0x10
- field public static final int USER_LOCKED_VISIBILITY = 2; // 0x2
}
public final class NotificationChannelGroup implements android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4931006..80e1002 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5517,6 +5517,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method public java.lang.String getDescription();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5526,6 +5527,7 @@
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public void setBypassDnd(boolean);
+ method public void setDescription(java.lang.String);
method public void setGroup(java.lang.String);
method public void setImportance(int);
method public void setLightColor(int);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 29c4520..92216d1 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -20,9 +20,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
import android.annotation.SystemApi;
import android.content.Intent;
import android.media.AudioAttributes;
@@ -46,8 +43,15 @@
*/
public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+ /**
+ * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
+ * limit.
+ */
+ private static final int MAX_TEXT_LENGTH = 1000;
+
private static final String TAG_CHANNEL = "channel";
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_DELETED = "deleted";
private static final String ATT_PRIORITY = "priority";
@@ -69,56 +73,46 @@
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_PRIORITY = 0x00000001;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_VISIBILITY = 0x00000002;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_IMPORTANCE = 0x00000004;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_LIGHTS = 0x00000008;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_VIBRATION = 0x00000010;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_SOUND = 0x00000020;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_ALLOWED = 0x00000040;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
/**
* @hide
*/
- @SystemApi
public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 0x00000100;
/**
* @hide
*/
- @SystemApi
public static final int[] LOCKABLE_FIELDS = new int[] {
USER_LOCKED_PRIORITY,
USER_LOCKED_VISIBILITY,
@@ -140,7 +134,8 @@
private static final boolean DEFAULT_SHOW_BADGE = true;
private final String mId;
- private CharSequence mName;
+ private String mName;
+ private String mDesc;
private int mImportance = DEFAULT_IMPORTANCE;
private boolean mBypassDnd;
private int mLockscreenVisibility = DEFAULT_VISIBILITY;
@@ -158,19 +153,19 @@
/**
* Creates a notification channel.
*
- * @param id The id of the channel. Must be unique per package.
- * @param name The user visible name of the channel. Unchangeable once created; use this
- * constructor if the channel represents a user-defined category that does not
- * need to be translated. You can rename this channel when the system
+ * @param id The id of the channel. Must be unique per package. The value may be truncated if
+ * it is too long.
+ * @param name The user visible name of the channel. You can rename this channel when the system
* locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
- * broadcast.
+ * broadcast. The recommended maximum length is 40 characters; the value may be
+ * truncated if it is too long.
* @param importance The importance of the channel. This controls how interruptive notifications
* posted to this channel are. See e.g.
* {@link NotificationManager#IMPORTANCE_DEFAULT}.
*/
public NotificationChannel(String id, CharSequence name, int importance) {
- this.mId = id;
- this.mName = name;
+ this.mId = getTrimmedString(id);
+ this.mName = name != null ? getTrimmedString(name.toString()) : null;
this.mImportance = importance;
}
@@ -180,7 +175,16 @@
} else {
mId = null;
}
- mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ if (in.readByte() != 0) {
+ mName = in.readString();
+ } else {
+ mName = null;
+ }
+ if (in.readByte() != 0) {
+ mDesc = in.readString();
+ } else {
+ mDesc = null;
+ }
mImportance = in.readInt();
mBypassDnd = in.readByte() != 0;
mLockscreenVisibility = in.readInt();
@@ -212,7 +216,18 @@
} else {
dest.writeByte((byte) 0);
}
- TextUtils.writeToParcel(mName, dest, flags);
+ if (mName != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mName);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ if (mDesc != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mDesc);
+ } else {
+ dest.writeByte((byte) 0);
+ }
dest.writeInt(mImportance);
dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
dest.writeInt(mLockscreenVisibility);
@@ -257,46 +272,33 @@
mDeleted = deleted;
}
+ // Modifiable by apps post channel creation
+
/**
- * Sets the name of this channel.
+ * Sets the user visible name of this channel.
+ *
+ * <p>The recommended maximum length is 40 characters; the value may be truncated if it is too
+ * long.
*/
public void setName(CharSequence name) {
- mName = name;
- }
-
- // Modifiable by a notification ranker.
-
- /**
- * Sets whether or not notifications posted to this channel can interrupt the user in
- * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
- *
- * Only modifiable by the system and notification ranker.
- */
- public void setBypassDnd(boolean bypassDnd) {
- this.mBypassDnd = bypassDnd;
+ mName = name != null ? getTrimmedString(name.toString()) : null;
}
/**
- * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
- * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+ * Sets the user visible description of this channel.
*
- * Only modifiable by the system and notification ranker.
+ * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
+ * long.
*/
- public void setLockscreenVisibility(int lockscreenVisibility) {
- this.mLockscreenVisibility = lockscreenVisibility;
+ public void setDescription(String description) {
+ mDesc = getTrimmedString(description);
}
- /**
- * Sets the level of interruption of this notification channel.
- *
- * Only modifiable by the system and notification ranker.
- *
- * @param importance the amount the user should be interrupted by notifications from this
- * channel. See e.g.
- * {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
- */
- public void setImportance(int importance) {
- this.mImportance = importance;
+ private String getTrimmedString(String input) {
+ if (input != null && input.length() > MAX_TEXT_LENGTH) {
+ return input.substring(0, MAX_TEXT_LENGTH);
+ }
+ return input;
}
// Modifiable by apps on channel creation.
@@ -385,6 +387,43 @@
}
/**
+ * Sets the level of interruption of this notification channel.
+ *
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#notify(String, int, Notification)}.
+ *
+ * @param importance the amount the user should be interrupted by notifications from this
+ * channel. See e.g.
+ * {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
+ */
+ public void setImportance(int importance) {
+ this.mImportance = importance;
+ }
+
+ // Modifiable by a notification ranker.
+
+ /**
+ * Sets whether or not notifications posted to this channel can interrupt the user in
+ * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
+ *
+ * Only modifiable by the system and notification ranker.
+ */
+ public void setBypassDnd(boolean bypassDnd) {
+ this.mBypassDnd = bypassDnd;
+ }
+
+ /**
+ * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
+ * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
+ *
+ * Only modifiable by the system and notification ranker.
+ */
+ public void setLockscreenVisibility(int lockscreenVisibility) {
+ this.mLockscreenVisibility = lockscreenVisibility;
+ }
+
+
+ /**
* Returns the id of this channel.
*/
public String getId() {
@@ -394,11 +433,18 @@
/**
* Returns the user visible name of this channel.
*/
- public @Nullable CharSequence getName() {
+ public CharSequence getName() {
return mName;
}
/**
+ * Returns the user visible description of this channel.
+ */
+ public String getDescription() {
+ return mDesc;
+ }
+
+ /**
* Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for
* notifications posted to this channel.
*/
@@ -507,6 +553,7 @@
@SystemApi
public void populateFromXml(XmlPullParser parser) {
// Name, id, and importance are set in the constructor.
+ setDescription(parser.getAttributeValue(null, ATT_DESC));
setBypassDnd(Notification.PRIORITY_DEFAULT
!= safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
@@ -531,6 +578,9 @@
if (getName() != null) {
out.attribute(null, ATT_NAME, getName().toString());
}
+ if (getDescription() != null) {
+ out.attribute(null, ATT_DESC, getDescription());
+ }
if (getImportance() != DEFAULT_IMPORTANCE) {
out.attribute(
null, ATT_IMPORTANCE, Integer.toString(getImportance()));
@@ -588,6 +638,7 @@
JSONObject record = new JSONObject();
record.put(ATT_ID, getId());
record.put(ATT_NAME, getName());
+ record.put(ATT_DESC, getDescription());
if (getImportance() != DEFAULT_IMPORTANCE) {
record.put(ATT_IMPORTANCE,
NotificationListenerService.Ranking.importanceToString(getImportance()));
@@ -718,6 +769,10 @@
if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
return false;
}
+ if (getDescription() != null ? !getDescription().equals(that.getDescription())
+ : that.getDescription() != null) {
+ return false;
+ }
if (getSound() != null ? !getSound().equals(that.getSound()) : that.getSound() != null) {
return false;
}
@@ -734,6 +789,7 @@
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 + getImportance();
result = 31 * result + (mBypassDnd ? 1 : 0);
result = 31 * result + getLockscreenVisibility();
@@ -755,6 +811,7 @@
return "NotificationChannel{" +
"mId='" + mId + '\'' +
", mName=" + mName +
+ ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "") +
", mImportance=" + mImportance +
", mBypassDnd=" + mBypassDnd +
", mLockscreenVisibility=" + mLockscreenVisibility +
diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java
index 2b0cd04..852af8a 100644
--- a/core/java/android/app/NotificationChannelGroup.java
+++ b/core/java/android/app/NotificationChannelGroup.java
@@ -40,6 +40,12 @@
*/
public final class NotificationChannelGroup implements Parcelable {
+ /**
+ * The maximum length for text fields in a NotificationChannelGroup. Fields will be truncated at
+ * this limit.
+ */
+ private static final int MAX_TEXT_LENGTH = 1000;
+
private static final String TAG_GROUP = "channelGroup";
private static final String ATT_NAME = "name";
private static final String ATT_ID = "id";
@@ -51,14 +57,16 @@
/**
* Creates a notification channel group.
*
- * @param id The id of the group. Must be unique per package.
+ * @param id The id of the group. Must be unique per package. the value may be truncated if
+ * it is too long.
* @param name The user visible name of the group. You can rename this group when the system
* locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
- * broadcast.
+ * broadcast. <p>The recommended maximum length is 40 characters; the value may be
+ * truncated if it is too long.
*/
public NotificationChannelGroup(String id, CharSequence name) {
- this.mId = id;
- this.mName = name;
+ this.mId = getTrimmedString(id);
+ this.mName = name != null ? getTrimmedString(name.toString()) : null;
}
protected NotificationChannelGroup(Parcel in) {
@@ -71,6 +79,13 @@
in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader());
}
+ private String getTrimmedString(String input) {
+ if (input != null && input.length() > MAX_TEXT_LENGTH) {
+ return input.substring(0, MAX_TEXT_LENGTH);
+ }
+ return input;
+ }
+
@Override
public void writeToParcel(Parcel dest, int flags) {
if (mId != null) {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 0379970..097df31 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -421,8 +421,12 @@
/**
* Creates a notification channel that notifications can be posted to.
*
- * This can also be used to restore a deleted channel and to rename an existing channel. All
- * other fields are ignored for channels that already exist.
+ * This can also be used to restore a deleted channel and to update an existing channel's
+ * name and description. 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
+ * named 'John Doe' that represents messages from a 'John Doe', and 'John Doe' changes his name
+ * to 'John Smith,' the channel can be renamed to match.
+ * 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
* value. If the provided channel is malformed, a RemoteException will be
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index ce79465..a42a70e 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -201,7 +201,7 @@
// Channels
if (TAG_CHANNEL.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
- CharSequence channelName = parser.getAttributeValue(null, ATT_NAME);
+ String channelName = parser.getAttributeValue(null, ATT_NAME);
int channelImportance =
safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
@@ -546,7 +546,8 @@
existing.setDeleted(false);
}
- existing.setName(channel.getName());
+ existing.setName(channel.getName().toString());
+ existing.setDescription(channel.getDescription());
MetricsLogger.action(getChannelLog(channel, pkg));
updateConfig();
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 5a94018..af44264 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -208,6 +208,7 @@
private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
assertEquals(expected.getId(), actual.getId());
assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
assertEquals(expected.getImportance(), actual.getImportance());
@@ -283,6 +284,7 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setDescription("descriptions for all");
channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel2.enableLights(true);
channel2.setBypassDnd(true);