summaryrefslogtreecommitdiff
path: root/core/java/android/app/NotificationChannel.java
diff options
context:
space:
mode:
authorJulia Reynolds <juliacr@google.com>2016-08-16 15:00:25 -0400
committerJulia Reynolds <juliacr@google.com>2016-09-15 08:42:35 -0400
commitb5e44b796618c376cf535e43aaa86ea4522e7770 (patch)
treee68121690f5cc9baccad2b1d37fd037b8fc92a93 /core/java/android/app/NotificationChannel.java
parent3cece22080fea16eccf41b62a5222774b194545f (diff)
Add Notification channels
In this iteration: -Every app gets a default channel that notifications will be posted to if they don't specify a channel themselves. The default channel inherits app-wide settings on upgrade. -Apps can create new channels without user approval, but apps cannot change the name of a channel once created, nor can they ever set the importance. - When a notification is posted: - If the channel is marked as 'vibrates', vibration will be applied to notifications that lack a vibration. unlike the default notification flag, notifications will retain their custom vibration if given - Same with sound and lights - A notification's importance is the min of the app and channel importance - A notification can bypass dnd if: either the app or channel settings say it can - A notification's show on lockscreen setting comes from the app first, and the channel second if the app has no preference Tests: in cl. also there's a cl for cts and a test app. Change-Id: I630f99df655800b00586dcfab538d320d04fe0f0
Diffstat (limited to 'core/java/android/app/NotificationChannel.java')
-rw-r--r--core/java/android/app/NotificationChannel.java382
1 files changed, 382 insertions, 0 deletions
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
new file mode 100644
index 000000000000..530b8bb7f3eb
--- /dev/null
+++ b/core/java/android/app/NotificationChannel.java
@@ -0,0 +1,382 @@
+package android.app;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.annotation.SystemApi;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.notification.NotificationListenerService;
+import android.text.TextUtils;
+
+import java.io.IOException;
+
+/**
+ * A representation of settings that apply to a collection of similarly themed notifications.
+ */
+public final class NotificationChannel implements Parcelable {
+
+ /**
+ * The id of the default channel for an app. All notifications posted without a notification
+ * channel specified are posted to this channel.
+ */
+ public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
+
+ private static final String TAG_CHANNEL = "channel";
+ private static final String ATT_NAME = "name";
+ private static final String ATT_ID = "id";
+ private static final String ATT_PRIORITY = "priority";
+ private static final String ATT_VISIBILITY = "visibility";
+ private static final String ATT_IMPORTANCE = "importance";
+ private static final String ATT_LIGHTS = "lights";
+ private static final String ATT_VIBRATION = "vibration";
+ private static final String ATT_DEFAULT_RINGTONE = "ringtone";
+
+ private static final int DEFAULT_VISIBILITY =
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+ private static final int DEFAULT_IMPORTANCE =
+ NotificationListenerService.Ranking.IMPORTANCE_UNSPECIFIED;
+
+ private final String mId;
+ private CharSequence mName;
+ private int mImportance = DEFAULT_IMPORTANCE;
+ private boolean mBypassDnd;
+ private int mLockscreenVisibility = DEFAULT_VISIBILITY;
+ private Uri mRingtone;
+ private boolean mLights;
+ private boolean mVibration;
+
+ /**
+ * 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.
+ */
+ public NotificationChannel(String id, CharSequence name) {
+ this.mId = id;
+ this.mName = name;
+ }
+
+ protected NotificationChannel(Parcel in) {
+ if (in.readByte() != 0) {
+ mId = in.readString();
+ } else {
+ mId = null;
+ }
+ mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mImportance = in.readInt();
+ mBypassDnd = in.readByte() != 0;
+ mLockscreenVisibility = in.readInt();
+ if (in.readByte() != 0) {
+ mRingtone = Uri.CREATOR.createFromParcel(in);
+ } else {
+ mRingtone = null;
+ }
+ mLights = in.readByte() != 0;
+ mVibration = in.readByte() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mId != null) {
+ dest.writeByte((byte) 1);
+ dest.writeString(mId);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeInt(mImportance);
+ dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
+ dest.writeInt(mLockscreenVisibility);
+ if (mRingtone != null) {
+ dest.writeByte((byte) 1);
+ mRingtone.writeToParcel(dest, 0);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ dest.writeByte(mLights ? (byte) 1 : (byte) 0);
+ dest.writeByte(mVibration ? (byte) 1 : (byte) 0);
+ }
+
+ // Only modifiable by users.
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setName(CharSequence name) {
+ this.mName = name;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setImportance(int importance) {
+ this.mImportance = importance;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setBypassDnd(boolean bypassDnd) {
+ this.mBypassDnd = bypassDnd;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void setLockscreenVisibility(int lockscreenVisibility) {
+ this.mLockscreenVisibility = lockscreenVisibility;
+ }
+
+ // Modifiable by apps.
+
+ /**
+ * Sets the ringtone that should be played for notifications posted to this channel if
+ * the notifications don't supply a ringtone.
+ */
+ public void setDefaultRingtone(Uri defaultRingtone) {
+ this.mRingtone = defaultRingtone;
+ }
+
+ /**
+ * Sets whether notifications posted to this channel should display notification lights,
+ * on devices that support that feature.
+ */
+ public void setLights(boolean lights) {
+ this.mLights = lights;
+ }
+
+ /**
+ * Sets whether notification posted to this channel should vibrate, even if individual
+ * notifications are marked as having vibration.
+ */
+ public void setVibration(boolean vibration) {
+ this.mVibration = vibration;
+ }
+
+ /**
+ * Returns the id of this channel.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the user visible name of this channel.
+ */
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for
+ * notifications posted to this channel.
+ */
+ public int getImportance() {
+ return mImportance;
+ }
+
+ /**
+ * Whether or not notifications posted to this channel can bypass the Do Not Disturb
+ * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
+ */
+ public boolean canBypassDnd() {
+ return mBypassDnd;
+ }
+
+ /**
+ * Returns the notification sound for this channel.
+ */
+ public Uri getDefaultRingtone() {
+ return mRingtone;
+ }
+
+ /**
+ * Returns whether notifications posted to this channel trigger notification lights.
+ */
+ public boolean shouldShowLights() {
+ return mLights;
+ }
+
+ /**
+ * Returns whether notifications posted to this channel always vibrate.
+ */
+ public boolean shouldVibrate() {
+ return mVibration;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public int getLockscreenVisibility() {
+ return mLockscreenVisibility;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void populateFromXml(XmlPullParser parser) {
+ // Name and id are set in the constructor.
+ setImportance(safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE));
+ setBypassDnd(Notification.PRIORITY_DEFAULT
+ != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
+ setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
+ setDefaultRingtone(safeUri(parser, ATT_DEFAULT_RINGTONE));
+ setLights(safeBool(parser, ATT_LIGHTS, false));
+ setVibration(safeBool(parser, ATT_VIBRATION, false));
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public void writeXml(XmlSerializer out) throws IOException {
+ out.startTag(null, TAG_CHANNEL);
+ out.attribute(null, ATT_ID, getId());
+ out.attribute(null, ATT_NAME, getName().toString());
+ if (getImportance() != DEFAULT_IMPORTANCE) {
+ out.attribute(
+ null, ATT_IMPORTANCE, Integer.toString(getImportance()));
+ }
+ if (canBypassDnd()) {
+ out.attribute(
+ null, ATT_PRIORITY, Integer.toString(Notification.PRIORITY_MAX));
+ }
+ if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY,
+ Integer.toString(getLockscreenVisibility()));
+ }
+ if (getDefaultRingtone() != null) {
+ out.attribute(null, ATT_DEFAULT_RINGTONE, getDefaultRingtone().toString());
+ }
+ if (shouldShowLights()) {
+ out.attribute(null, ATT_LIGHTS, Boolean.toString(shouldShowLights()));
+ }
+ if (shouldVibrate()) {
+ out.attribute(null, ATT_VIBRATION, Boolean.toString(shouldVibrate()));
+ }
+ out.endTag(null, TAG_CHANNEL);
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ public JSONObject toJson() throws JSONException {
+ JSONObject record = new JSONObject();
+ record.put(ATT_ID, getId());
+ record.put(ATT_NAME, getName());
+ if (getImportance() != DEFAULT_IMPORTANCE) {
+ record.put(ATT_IMPORTANCE,
+ NotificationListenerService.Ranking.importanceToString(getImportance()));
+ }
+ if (canBypassDnd()) {
+ record.put(ATT_PRIORITY, Notification.PRIORITY_MAX);
+ }
+ if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
+ record.put(ATT_VISIBILITY, Notification.visibilityToString(getLockscreenVisibility()));
+ }
+ if (getDefaultRingtone() != null) {
+ record.put(ATT_DEFAULT_RINGTONE, getDefaultRingtone().toString());
+ }
+ record.put(ATT_LIGHTS, Boolean.toString(shouldShowLights()));
+ record.put(ATT_VIBRATION, Boolean.toString(shouldVibrate()));
+
+ return record;
+ }
+
+ private static Uri safeUri(XmlPullParser parser, String att) {
+ final String val = parser.getAttributeValue(null, att);
+ return val == null ? null : Uri.parse(val);
+ }
+
+ private static int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseInt(val, defValue);
+ }
+
+ private static int tryParseInt(String value, int defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+ 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);
+ }
+
+ public static final Creator<NotificationChannel> CREATOR = new Creator<NotificationChannel>() {
+ @Override
+ public NotificationChannel createFromParcel(Parcel in) {
+ return new NotificationChannel(in);
+ }
+
+ @Override
+ public NotificationChannel[] newArray(int size) {
+ return new NotificationChannel[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NotificationChannel that = (NotificationChannel) o;
+
+ if (mImportance != that.mImportance) return false;
+ if (mBypassDnd != that.mBypassDnd) return false;
+ if (mLockscreenVisibility != that.mLockscreenVisibility) return false;
+ if (mLights != that.mLights) return false;
+ if (mVibration != that.mVibration) return false;
+ if (!mId.equals(that.mId)) return false;
+ if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
+ return mRingtone != null ? mRingtone.equals(
+ that.mRingtone) : that.mRingtone == null;
+ }
+
+ @Override
+ public String toString() {
+ return "NotificationChannel{" +
+ "mId='" + mId + '\'' +
+ ", mName=" + mName +
+ ", mImportance=" + mImportance +
+ ", mBypassDnd=" + mBypassDnd +
+ ", mLockscreenVisibility=" + mLockscreenVisibility +
+ ", mRingtone='" + mRingtone + '\'' +
+ ", mLights=" + mLights +
+ ", mVibration=" + mVibration +
+ '}';
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mId.hashCode();
+ result = 31 * result + (mName != null ? mName.hashCode() : 0);
+ result = 31 * result + mImportance;
+ result = 31 * result + (mBypassDnd ? 1 : 0);
+ result = 31 * result + mLockscreenVisibility;
+ result = 31 * result + (mRingtone != null ? mRingtone.hashCode() : 0);
+ result = 31 * result + (mLights ? 1 : 0);
+ result = 31 * result + (mVibration ? 1 : 0);
+ return result;
+ }
+}