NotificationChannel.java revision 924eed1ca6d3fec5dae7eb0f9c11b8f23f628697
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.app;
17
18import org.json.JSONException;
19import org.json.JSONObject;
20import org.xmlpull.v1.XmlPullParser;
21import org.xmlpull.v1.XmlSerializer;
22
23import android.annotation.SystemApi;
24import android.net.Uri;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.service.notification.NotificationListenerService;
28import android.text.TextUtils;
29
30import java.io.IOException;
31import java.util.Arrays;
32
33/**
34 * A representation of settings that apply to a collection of similarly themed notifications.
35 */
36public final class NotificationChannel implements Parcelable {
37
38    /**
39     * The id of the default channel for an app. All notifications posted without a notification
40     * channel specified are posted to this channel.
41     */
42    public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
43
44    private static final String TAG_CHANNEL = "channel";
45    private static final String ATT_NAME = "name";
46    private static final String ATT_ID = "id";
47    private static final String ATT_DELETED = "deleted";
48    private static final String ATT_PRIORITY = "priority";
49    private static final String ATT_VISIBILITY = "visibility";
50    private static final String ATT_IMPORTANCE = "importance";
51    private static final String ATT_LIGHTS = "lights";
52    private static final String ATT_VIBRATION = "vibration";
53    private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
54    private static final String ATT_SOUND = "sound";
55    //TODO: add audio attributes support
56    private static final String ATT_AUDIO_ATTRIBUTES = "audio_attributes";
57    private static final String ATT_SHOW_BADGE = "show_badge";
58    private static final String ATT_USER_LOCKED = "locked";
59    private static final String DELIMITER = ",";
60
61    /**
62     * @hide
63     */
64    @SystemApi
65    public static final int USER_LOCKED_PRIORITY = 0x00000001;
66    /**
67     * @hide
68     */
69    @SystemApi
70    public static final int USER_LOCKED_VISIBILITY = 0x00000002;
71    /**
72     * @hide
73     */
74    @SystemApi
75    public static final int USER_LOCKED_IMPORTANCE = 0x00000004;
76    /**
77     * @hide
78     */
79    @SystemApi
80    public static final int USER_LOCKED_LIGHTS = 0x00000008;
81    /**
82     * @hide
83     */
84    @SystemApi
85    public static final int USER_LOCKED_VIBRATION = 0x00000010;
86    /**
87     * @hide
88     */
89    @SystemApi
90    public static final int USER_LOCKED_SOUND = 0x00000020;
91
92    /**
93     * @hide
94     */
95    @SystemApi
96    public static final int USER_LOCKED_ALLOWED = 0x00000040;
97
98    /**
99     * @hide
100     */
101    @SystemApi
102    public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
103
104    /**
105     * @hide
106     */
107    @SystemApi
108    public static final int[] LOCKABLE_FIELDS = new int[] {
109            USER_LOCKED_PRIORITY,
110            USER_LOCKED_VISIBILITY,
111            USER_LOCKED_IMPORTANCE,
112            USER_LOCKED_LIGHTS,
113            USER_LOCKED_VIBRATION,
114            USER_LOCKED_SOUND,
115            USER_LOCKED_ALLOWED,
116            USER_LOCKED_SHOW_BADGE
117    };
118
119
120    private static final int DEFAULT_VISIBILITY =
121            NotificationManager.VISIBILITY_NO_OVERRIDE;
122    private static final int DEFAULT_IMPORTANCE =
123            NotificationManager.IMPORTANCE_UNSPECIFIED;
124    private static final boolean DEFAULT_DELETED = false;
125    private static final boolean DEFAULT_SHOW_BADGE = true;
126
127    private final String mId;
128    private CharSequence mName;
129    private int mImportance = DEFAULT_IMPORTANCE;
130    private boolean mBypassDnd;
131    private int mLockscreenVisibility = DEFAULT_VISIBILITY;
132    private Uri mSound;
133    private boolean mLights;
134    private long[] mVibration;
135    private int mUserLockedFields;
136    private boolean mVibrationEnabled;
137    private boolean mShowBadge = DEFAULT_SHOW_BADGE;
138    private boolean mDeleted = DEFAULT_DELETED;
139
140    /**
141     * Creates a notification channel.
142     *
143     * @param id The id of the channel. Must be unique per package.
144     * @param name The user visible name of the channel.
145     * @param importance The importance of the channel. This controls how interruptive notifications
146     *                   posted to this channel are. See e.g.
147     *                   {@link NotificationManager#IMPORTANCE_DEFAULT}.
148     */
149    public NotificationChannel(String id, CharSequence name, int importance) {
150        this.mId = id;
151        this.mName = name;
152        this.mImportance = importance;
153    }
154
155    protected NotificationChannel(Parcel in) {
156        if (in.readByte() != 0) {
157            mId = in.readString();
158        } else {
159            mId = null;
160        }
161        mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
162        mImportance = in.readInt();
163        mBypassDnd = in.readByte() != 0;
164        mLockscreenVisibility = in.readInt();
165        if (in.readByte() != 0) {
166            mSound = Uri.CREATOR.createFromParcel(in);
167        } else {
168            mSound = null;
169        }
170        mLights = in.readByte() != 0;
171        mVibration = in.createLongArray();
172        mUserLockedFields = in.readInt();
173        mVibrationEnabled = in.readByte() != 0;
174        mShowBadge = in.readByte() != 0;
175        mDeleted = in.readByte() != 0;
176    }
177
178    @Override
179    public void writeToParcel(Parcel dest, int flags) {
180        if (mId != null) {
181            dest.writeByte((byte) 1);
182            dest.writeString(mId);
183        } else {
184            dest.writeByte((byte) 0);
185        }
186        TextUtils.writeToParcel(mName, dest, flags);
187        dest.writeInt(mImportance);
188        dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
189        dest.writeInt(mLockscreenVisibility);
190        if (mSound != null) {
191            dest.writeByte((byte) 1);
192            mSound.writeToParcel(dest, 0);
193        } else {
194            dest.writeByte((byte) 0);
195        }
196        dest.writeByte(mLights ? (byte) 1 : (byte) 0);
197        dest.writeLongArray(mVibration);
198        dest.writeInt(mUserLockedFields);
199        dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
200        dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
201        dest.writeByte(mDeleted ? (byte) 1 : (byte) 0);
202    }
203
204    /**
205     * @hide
206     */
207    @SystemApi
208    public void lockFields(int field) {
209        mUserLockedFields |= field;
210    }
211
212    /**
213     * @hide
214     */
215    @SystemApi
216    public void setDeleted(boolean deleted) {
217        mDeleted = deleted;
218    }
219
220    // Modifiable by a notification ranker.
221
222    /**
223     * Sets whether or not notifications posted to this channel can interrupt the user in
224     * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
225     *
226     * Only modifiable by the system and notification ranker.
227     */
228    public void setBypassDnd(boolean bypassDnd) {
229        this.mBypassDnd = bypassDnd;
230    }
231
232    /**
233     * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
234     * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
235     *
236     * Only modifiable by the system and notification ranker.
237     */
238    public void setLockscreenVisibility(int lockscreenVisibility) {
239        this.mLockscreenVisibility = lockscreenVisibility;
240    }
241
242    /**
243     * Sets the level of interruption of this notification channel.
244     *
245     * Only modifiable by the system and notification ranker.
246     *
247     * @param importance the amount the user should be interrupted by notifications from this
248     *                   channel. See e.g.
249     *                   {@link android.app.NotificationManager#IMPORTANCE_DEFAULT}.
250     */
251    public void setImportance(int importance) {
252        this.mImportance = importance;
253    }
254
255    // Modifiable by apps on channel creation.
256
257    /**
258     * Sets whether notifications posted to this channel can appear as application icon badges
259     * in a Launcher.
260     *
261     * @param showBadge true if badges should be allowed to be shown.
262     */
263    public void setShowBadge(boolean showBadge) {
264        this.mShowBadge = showBadge;
265    }
266
267    /**
268     * Sets the sound that should be played for notifications posted to this channel if
269     * the notifications don't supply a sound. Only modifiable before the channel is submitted
270     * to the NotificationManager.
271     */
272    public void setSound(Uri sound) {
273        this.mSound = sound;
274    }
275
276    /**
277     * Sets whether notifications posted to this channel should display notification lights,
278     * on devices that support that feature. Only modifiable before the channel is submitted to
279     * the NotificationManager.
280     */
281    public void setLights(boolean lights) {
282        this.mLights = lights;
283    }
284
285    /**
286     * Sets whether notification posted to this channel should vibrate. The vibration pattern can
287     * be set with {@link #setVibrationPattern(long[])}. Only modifiable before the channel is
288     * submitted to the NotificationManager.
289     */
290    public void enableVibration(boolean vibration) {
291        this.mVibrationEnabled = vibration;
292    }
293
294    /**
295     * Sets whether notification posted to this channel should vibrate. Only modifiable before the
296     * channel is submitted to the NotificationManager.
297     */
298    public void setVibrationPattern(long[] vibrationPattern) {
299        this.mVibration = vibrationPattern;
300    }
301
302    /**
303     * Returns the id of this channel.
304     */
305    public String getId() {
306        return mId;
307    }
308
309    /**
310     * Returns the user visible name of this channel.
311     */
312    public CharSequence getName() {
313        return mName;
314    }
315
316    /**
317     * Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for
318     * notifications posted to this channel.
319     */
320    public int getImportance() {
321        return mImportance;
322    }
323
324    /**
325     * Whether or not notifications posted to this channel can bypass the Do Not Disturb
326     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
327     */
328    public boolean canBypassDnd() {
329        return mBypassDnd;
330    }
331
332    /**
333     * Returns the notification sound for this channel.
334     */
335    public Uri getSound() {
336        return mSound;
337    }
338
339    /**
340     * Returns whether notifications posted to this channel trigger notification lights.
341     */
342    public boolean shouldShowLights() {
343        return mLights;
344    }
345
346    /**
347     * Returns whether notifications posted to this channel always vibrate.
348     */
349    public boolean shouldVibrate() {
350        return mVibrationEnabled;
351    }
352
353    /**
354     * Returns the vibration pattern for notifications posted to this channel. Will be ignored if
355     * vibration is not enabled ({@link #shouldVibrate()}.
356     */
357    public long[] getVibrationPattern() {
358        return mVibration;
359    }
360
361    /**
362     * Returns whether or not notifications posted to this channel are shown on the lockscreen in
363     * full or redacted form.
364     */
365    public int getLockscreenVisibility() {
366        return mLockscreenVisibility;
367    }
368
369    /**
370     * Returns whether notifications posted to this channel can appear as badges in a Launcher
371     * application.
372     *
373     * Note that badging may be disabled for other reasons.
374     */
375    public boolean canShowBadge() {
376        return mShowBadge;
377    }
378
379    /**
380     * @hide
381     */
382    @SystemApi
383    public boolean isDeleted() {
384        return mDeleted;
385    }
386
387    /**
388     * @hide
389     */
390    @SystemApi
391    public int getUserLockedFields() {
392        return mUserLockedFields;
393    }
394
395    /**
396     * @hide
397     */
398    @SystemApi
399    public void populateFromXml(XmlPullParser parser) {
400        // Name, id, and importance are set in the constructor.
401        setBypassDnd(Notification.PRIORITY_DEFAULT
402                != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
403        setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
404        setSound(safeUri(parser, ATT_SOUND));
405        setLights(safeBool(parser, ATT_LIGHTS, false));
406        enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
407        setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
408        setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
409        setDeleted(safeBool(parser, ATT_DELETED, false));
410        lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
411    }
412
413    /**
414     * @hide
415     */
416    @SystemApi
417    public void writeXml(XmlSerializer out) throws IOException {
418        out.startTag(null, TAG_CHANNEL);
419        out.attribute(null, ATT_ID, getId());
420        out.attribute(null, ATT_NAME, getName().toString());
421        if (getImportance() != DEFAULT_IMPORTANCE) {
422            out.attribute(
423                    null, ATT_IMPORTANCE, Integer.toString(getImportance()));
424        }
425        if (canBypassDnd()) {
426            out.attribute(
427                    null, ATT_PRIORITY, Integer.toString(Notification.PRIORITY_MAX));
428        }
429        if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
430            out.attribute(null, ATT_VISIBILITY,
431                    Integer.toString(getLockscreenVisibility()));
432        }
433        if (getSound() != null) {
434            out.attribute(null, ATT_SOUND, getSound().toString());
435        }
436        if (shouldShowLights()) {
437            out.attribute(null, ATT_LIGHTS, Boolean.toString(shouldShowLights()));
438        }
439        if (shouldVibrate()) {
440            out.attribute(null, ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
441        }
442        if (getVibrationPattern() != null) {
443            out.attribute(null, ATT_VIBRATION, longArrayToString(getVibrationPattern()));
444        }
445        if (getUserLockedFields() != 0) {
446            out.attribute(null, ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
447        }
448        if (canShowBadge()) {
449            out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
450        }
451        if (isDeleted()) {
452            out.attribute(null, ATT_DELETED, Boolean.toString(isDeleted()));
453        }
454
455        out.endTag(null, TAG_CHANNEL);
456    }
457
458    /**
459     * @hide
460     */
461    @SystemApi
462    public JSONObject toJson() throws JSONException {
463        JSONObject record = new JSONObject();
464        record.put(ATT_ID, getId());
465        record.put(ATT_NAME, getName());
466        if (getImportance() != DEFAULT_IMPORTANCE) {
467            record.put(ATT_IMPORTANCE,
468                    NotificationListenerService.Ranking.importanceToString(getImportance()));
469        }
470        if (canBypassDnd()) {
471            record.put(ATT_PRIORITY, Notification.PRIORITY_MAX);
472        }
473        if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
474            record.put(ATT_VISIBILITY, Notification.visibilityToString(getLockscreenVisibility()));
475        }
476        if (getSound() != null) {
477            record.put(ATT_SOUND, getSound().toString());
478        }
479        record.put(ATT_LIGHTS, Boolean.toString(shouldShowLights()));
480        record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
481        record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
482        record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
483        record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
484        record.put(ATT_DELETED, Boolean.toString(isDeleted()));
485        return record;
486    }
487
488    private static Uri safeUri(XmlPullParser parser, String att) {
489        final String val = parser.getAttributeValue(null, att);
490        return val == null ? null : Uri.parse(val);
491    }
492
493    private static int safeInt(XmlPullParser parser, String att, int defValue) {
494        final String val = parser.getAttributeValue(null, att);
495        return tryParseInt(val, defValue);
496    }
497
498    private static int tryParseInt(String value, int defValue) {
499        if (TextUtils.isEmpty(value)) return defValue;
500        try {
501            return Integer.parseInt(value);
502        } catch (NumberFormatException e) {
503            return defValue;
504        }
505    }
506
507    private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
508        final String value = parser.getAttributeValue(null, att);
509        if (TextUtils.isEmpty(value)) return defValue;
510        return Boolean.parseBoolean(value);
511    }
512
513    private static long[] safeLongArray(XmlPullParser parser, String att, long[] defValue) {
514        final String attributeValue = parser.getAttributeValue(null, att);
515        if (TextUtils.isEmpty(attributeValue)) return defValue;
516        String[] values = attributeValue.split(DELIMITER);
517        long[] longValues = new long[values.length];
518        for (int i = 0; i < values.length; i++) {
519            try {
520                longValues[i] = Long.parseLong(values[i]);
521            } catch (NumberFormatException e) {
522                longValues[i] = 0;
523            }
524        }
525        return longValues;
526    }
527
528    private static String longArrayToString(long[] values) {
529        StringBuffer sb = new StringBuffer();
530        for (int i = 0; i < values.length - 1; i++) {
531            sb.append(values[i]).append(DELIMITER);
532        }
533        sb.append(values[values.length - 1]);
534        return sb.toString();
535    }
536
537    public static final Creator<NotificationChannel> CREATOR = new Creator<NotificationChannel>() {
538        @Override
539        public NotificationChannel createFromParcel(Parcel in) {
540            return new NotificationChannel(in);
541        }
542
543        @Override
544        public NotificationChannel[] newArray(int size) {
545            return new NotificationChannel[size];
546        }
547    };
548
549    @Override
550    public int describeContents() {
551        return 0;
552    }
553
554    @Override
555    public boolean equals(Object o) {
556        if (this == o) return true;
557        if (o == null || getClass() != o.getClass()) return false;
558
559        NotificationChannel that = (NotificationChannel) o;
560
561        if (mImportance != that.mImportance) return false;
562        if (mBypassDnd != that.mBypassDnd) return false;
563        if (mLockscreenVisibility != that.mLockscreenVisibility) return false;
564        if (mLights != that.mLights) return false;
565        if (mUserLockedFields != that.mUserLockedFields) return false;
566        if (mVibrationEnabled != that.mVibrationEnabled) return false;
567        if (mShowBadge != that.mShowBadge) return false;
568        if (mDeleted != that.mDeleted) return false;
569        if (mId != null ? !mId.equals(that.mId) : that.mId != null) return false;
570        if (mName != null ? !mName.equals(that.mName) : that.mName != null) return false;
571        if (mSound != null ? !mSound.equals(that.mSound) : that.mSound != null) return false;
572        return Arrays.equals(mVibration, that.mVibration);
573
574    }
575
576    @Override
577    public int hashCode() {
578        int result = mId != null ? mId.hashCode() : 0;
579        result = 31 * result + (mName != null ? mName.hashCode() : 0);
580        result = 31 * result + mImportance;
581        result = 31 * result + (mBypassDnd ? 1 : 0);
582        result = 31 * result + mLockscreenVisibility;
583        result = 31 * result + (mSound != null ? mSound.hashCode() : 0);
584        result = 31 * result + (mLights ? 1 : 0);
585        result = 31 * result + Arrays.hashCode(mVibration);
586        result = 31 * result + mUserLockedFields;
587        result = 31 * result + (mVibrationEnabled ? 1 : 0);
588        result = 31 * result + (mShowBadge ? 1 : 0);
589        result = 31 * result + (mDeleted ? 1 : 0);
590        return result;
591    }
592
593    @Override
594    public String toString() {
595        return "NotificationChannel{" +
596                "mId='" + mId + '\'' +
597                ", mName=" + mName +
598                ", mImportance=" + mImportance +
599                ", mBypassDnd=" + mBypassDnd +
600                ", mLockscreenVisibility=" + mLockscreenVisibility +
601                ", mSound=" + mSound +
602                ", mLights=" + mLights +
603                ", mVibration=" + Arrays.toString(mVibration) +
604                ", mUserLockedFields=" + mUserLockedFields +
605                ", mVibrationEnabled=" + mVibrationEnabled +
606                ", mShowBadge=" + mShowBadge +
607                ", mDeleted=" + mDeleted +
608                '}';
609    }
610}
611