NotificationChannel.java revision 8d2b053611fe4a52602c125bf1f577c12083848a
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 android.annotation.Nullable;
19import android.annotation.SystemApi;
20import android.app.NotificationManager.Importance;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.media.AudioAttributes;
25import android.net.Uri;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.provider.Settings;
29import android.service.notification.NotificationListenerService;
30import android.text.TextUtils;
31import android.util.proto.ProtoOutputStream;
32
33import com.android.internal.util.Preconditions;
34
35import org.json.JSONException;
36import org.json.JSONObject;
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlSerializer;
39
40import java.io.IOException;
41import java.util.Arrays;
42
43/**
44 * A representation of settings that apply to a collection of similarly themed notifications.
45 */
46public final class NotificationChannel implements Parcelable {
47
48    /**
49     * The id of the default channel for an app. This id is reserved by the system. All
50     * notifications posted from apps targeting {@link android.os.Build.VERSION_CODES#N_MR1} or
51     * earlier without a notification channel specified are posted to this channel.
52     */
53    public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
54
55    /**
56     * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
57     * limit.
58     */
59    private static final int MAX_TEXT_LENGTH = 1000;
60
61    private static final String TAG_CHANNEL = "channel";
62    private static final String ATT_NAME = "name";
63    private static final String ATT_DESC = "desc";
64    private static final String ATT_ID = "id";
65    private static final String ATT_DELETED = "deleted";
66    private static final String ATT_PRIORITY = "priority";
67    private static final String ATT_VISIBILITY = "visibility";
68    private static final String ATT_IMPORTANCE = "importance";
69    private static final String ATT_LIGHTS = "lights";
70    private static final String ATT_LIGHT_COLOR = "light_color";
71    private static final String ATT_VIBRATION = "vibration";
72    private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
73    private static final String ATT_SOUND = "sound";
74    private static final String ATT_USAGE = "usage";
75    private static final String ATT_FLAGS = "flags";
76    private static final String ATT_CONTENT_TYPE = "content_type";
77    private static final String ATT_SHOW_BADGE = "show_badge";
78    private static final String ATT_USER_LOCKED = "locked";
79    private static final String ATT_GROUP = "group";
80    private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
81    private static final String DELIMITER = ",";
82
83    /**
84     * @hide
85     */
86    public static final int USER_LOCKED_PRIORITY = 0x00000001;
87    /**
88     * @hide
89     */
90    public static final int USER_LOCKED_VISIBILITY = 0x00000002;
91    /**
92     * @hide
93     */
94    public static final int USER_LOCKED_IMPORTANCE = 0x00000004;
95    /**
96     * @hide
97     */
98    public static final int USER_LOCKED_LIGHTS = 0x00000008;
99    /**
100     * @hide
101     */
102    public static final int USER_LOCKED_VIBRATION = 0x00000010;
103    /**
104     * @hide
105     */
106    public static final int USER_LOCKED_SOUND = 0x00000020;
107
108    /**
109     * @hide
110     */
111    public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
112
113    /**
114     * @hide
115     */
116    public static final int[] LOCKABLE_FIELDS = new int[] {
117            USER_LOCKED_PRIORITY,
118            USER_LOCKED_VISIBILITY,
119            USER_LOCKED_IMPORTANCE,
120            USER_LOCKED_LIGHTS,
121            USER_LOCKED_VIBRATION,
122            USER_LOCKED_SOUND,
123            USER_LOCKED_SHOW_BADGE,
124    };
125
126    private static final int DEFAULT_LIGHT_COLOR = 0;
127    private static final int DEFAULT_VISIBILITY =
128            NotificationManager.VISIBILITY_NO_OVERRIDE;
129    private static final int DEFAULT_IMPORTANCE =
130            NotificationManager.IMPORTANCE_UNSPECIFIED;
131    private static final boolean DEFAULT_DELETED = false;
132    private static final boolean DEFAULT_SHOW_BADGE = true;
133
134    private final String mId;
135    private String mName;
136    private String mDesc;
137    private int mImportance = DEFAULT_IMPORTANCE;
138    private boolean mBypassDnd;
139    private int mLockscreenVisibility = DEFAULT_VISIBILITY;
140    private Uri mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
141    private boolean mLights;
142    private int mLightColor = DEFAULT_LIGHT_COLOR;
143    private long[] mVibration;
144    // Bitwise representation of fields that have been changed by the user, preventing the app from
145    // making changes to these fields.
146    private int mUserLockedFields;
147    private boolean mVibrationEnabled;
148    private boolean mShowBadge = DEFAULT_SHOW_BADGE;
149    private boolean mDeleted = DEFAULT_DELETED;
150    private String mGroup;
151    private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
152    // If this is a blockable system notification channel.
153    private boolean mBlockableSystem = false;
154
155    /**
156     * Creates a notification channel.
157     *
158     * @param id The id of the channel. Must be unique per package. The value may be truncated if
159     *           it is too long.
160     * @param name The user visible name of the channel. You can rename this channel when the system
161     *             locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
162     *             broadcast. The recommended maximum length is 40 characters; the value may be
163     *             truncated if it is too long.
164     * @param importance The importance of the channel. This controls how interruptive notifications
165     *                   posted to this channel are.
166     */
167    public NotificationChannel(String id, CharSequence name, @Importance int importance) {
168        this.mId = getTrimmedString(id);
169        this.mName = name != null ? getTrimmedString(name.toString()) : null;
170        this.mImportance = importance;
171    }
172
173    /**
174     * @hide
175     */
176    protected NotificationChannel(Parcel in) {
177        if (in.readByte() != 0) {
178            mId = in.readString();
179        } else {
180            mId = null;
181        }
182        if (in.readByte() != 0) {
183            mName = in.readString();
184        } else {
185            mName = null;
186        }
187        if (in.readByte() != 0) {
188            mDesc = in.readString();
189        } else {
190            mDesc = null;
191        }
192        mImportance = in.readInt();
193        mBypassDnd = in.readByte() != 0;
194        mLockscreenVisibility = in.readInt();
195        if (in.readByte() != 0) {
196            mSound = Uri.CREATOR.createFromParcel(in);
197        } else {
198            mSound = null;
199        }
200        mLights = in.readByte() != 0;
201        mVibration = in.createLongArray();
202        mUserLockedFields = in.readInt();
203        mVibrationEnabled = in.readByte() != 0;
204        mShowBadge = in.readByte() != 0;
205        mDeleted = in.readByte() != 0;
206        if (in.readByte() != 0) {
207            mGroup = in.readString();
208        } else {
209            mGroup = null;
210        }
211        mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null;
212        mLightColor = in.readInt();
213        mBlockableSystem = in.readBoolean();
214    }
215
216    @Override
217    public void writeToParcel(Parcel dest, int flags) {
218        if (mId != null) {
219            dest.writeByte((byte) 1);
220            dest.writeString(mId);
221        } else {
222            dest.writeByte((byte) 0);
223        }
224        if (mName != null) {
225            dest.writeByte((byte) 1);
226            dest.writeString(mName);
227        } else {
228            dest.writeByte((byte) 0);
229        }
230        if (mDesc != null) {
231            dest.writeByte((byte) 1);
232            dest.writeString(mDesc);
233        } else {
234            dest.writeByte((byte) 0);
235        }
236        dest.writeInt(mImportance);
237        dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
238        dest.writeInt(mLockscreenVisibility);
239        if (mSound != null) {
240            dest.writeByte((byte) 1);
241            mSound.writeToParcel(dest, 0);
242        } else {
243            dest.writeByte((byte) 0);
244        }
245        dest.writeByte(mLights ? (byte) 1 : (byte) 0);
246        dest.writeLongArray(mVibration);
247        dest.writeInt(mUserLockedFields);
248        dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
249        dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
250        dest.writeByte(mDeleted ? (byte) 1 : (byte) 0);
251        if (mGroup != null) {
252            dest.writeByte((byte) 1);
253            dest.writeString(mGroup);
254        } else {
255            dest.writeByte((byte) 0);
256        }
257        if (mAudioAttributes != null) {
258            dest.writeInt(1);
259            mAudioAttributes.writeToParcel(dest, 0);
260        } else {
261            dest.writeInt(0);
262        }
263        dest.writeInt(mLightColor);
264        dest.writeBoolean(mBlockableSystem);
265    }
266
267    /**
268     * @hide
269     */
270    public void lockFields(int field) {
271        mUserLockedFields |= field;
272    }
273
274    /**
275     * @hide
276     */
277    public void unlockFields(int field) {
278        mUserLockedFields &= ~field;
279    }
280
281    /**
282     * @hide
283     */
284    public void setDeleted(boolean deleted) {
285        mDeleted = deleted;
286    }
287
288    /**
289     * @hide
290     */
291    public void setBlockableSystem(boolean blockableSystem) {
292        mBlockableSystem = blockableSystem;
293    }
294    // Modifiable by apps post channel creation
295
296    /**
297     * Sets the user visible name of this channel.
298     *
299     * <p>The recommended maximum length is 40 characters; the value may be truncated if it is too
300     * long.
301     */
302    public void setName(CharSequence name) {
303        mName = name != null ? getTrimmedString(name.toString()) : null;
304    }
305
306    /**
307     * Sets the user visible description of this channel.
308     *
309     * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
310     * long.
311     */
312    public void setDescription(String description) {
313        mDesc = getTrimmedString(description);
314    }
315
316    private String getTrimmedString(String input) {
317        if (input != null && input.length() > MAX_TEXT_LENGTH) {
318            return input.substring(0, MAX_TEXT_LENGTH);
319        }
320        return input;
321    }
322
323    // Modifiable by apps on channel creation.
324
325    /**
326     * Sets what group this channel belongs to.
327     *
328     * Group information is only used for presentation, not for behavior.
329     *
330     * Only modifiable before the channel is submitted to
331     * {@link NotificationManager#notify(String, int, Notification)}.
332     *
333     * @param groupId the id of a group created by
334     * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.
335     */
336    public void setGroup(String groupId) {
337        this.mGroup = groupId;
338    }
339
340    /**
341     * Sets whether notifications posted to this channel can appear as application icon badges
342     * in a Launcher.
343     *
344     * @param showBadge true if badges should be allowed to be shown.
345     */
346    public void setShowBadge(boolean showBadge) {
347        this.mShowBadge = showBadge;
348    }
349
350    /**
351     * Sets the sound that should be played for notifications posted to this channel and its
352     * audio attributes. Notification channels with an {@link #getImportance() importance} of at
353     * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
354     *
355     * Only modifiable before the channel is submitted to
356     * {@link NotificationManager#notify(String, int, Notification)}.
357     */
358    public void setSound(Uri sound, AudioAttributes audioAttributes) {
359        this.mSound = sound;
360        this.mAudioAttributes = audioAttributes;
361    }
362
363    /**
364     * Sets whether notifications posted to this channel should display notification lights,
365     * on devices that support that feature.
366     *
367     * Only modifiable before the channel is submitted to
368     * {@link NotificationManager#notify(String, int, Notification)}.
369     */
370    public void enableLights(boolean lights) {
371        this.mLights = lights;
372    }
373
374    /**
375     * Sets the notification light color for notifications posted to this channel, if lights are
376     * {@link #enableLights(boolean) enabled} on this channel and the device supports that feature.
377     *
378     * Only modifiable before the channel is submitted to
379     * {@link NotificationManager#notify(String, int, Notification)}.
380     */
381    public void setLightColor(int argb) {
382        this.mLightColor = argb;
383    }
384
385    /**
386     * Sets whether notification posted to this channel should vibrate. The vibration pattern can
387     * be set with {@link #setVibrationPattern(long[])}.
388     *
389     * Only modifiable before the channel is submitted to
390     * {@link NotificationManager#notify(String, int, Notification)}.
391     */
392    public void enableVibration(boolean vibration) {
393        this.mVibrationEnabled = vibration;
394    }
395
396    /**
397     * Sets the vibration pattern for notifications posted to this channel. If the provided
398     * pattern is valid (non-null, non-empty), will {@link #enableVibration(boolean)} enable
399     * vibration} as well. Otherwise, vibration will be disabled.
400     *
401     * Only modifiable before the channel is submitted to
402     * {@link NotificationManager#notify(String, int, Notification)}.
403     */
404    public void setVibrationPattern(long[] vibrationPattern) {
405        this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0;
406        this.mVibration = vibrationPattern;
407    }
408
409    /**
410     * Sets the level of interruption of this notification channel. Only
411     * modifiable before the channel is submitted to
412     * {@link NotificationManager#notify(String, int, Notification)}.
413     *
414     * @param importance the amount the user should be interrupted by
415     *            notifications from this channel.
416     */
417    public void setImportance(@Importance int importance) {
418        this.mImportance = importance;
419    }
420
421    // Modifiable by a notification ranker.
422
423    /**
424     * Sets whether or not notifications posted to this channel can interrupt the user in
425     * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
426     *
427     * Only modifiable by the system and notification ranker.
428     */
429    public void setBypassDnd(boolean bypassDnd) {
430        this.mBypassDnd = bypassDnd;
431    }
432
433    /**
434     * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
435     * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
436     *
437     * Only modifiable by the system and notification ranker.
438     */
439    public void setLockscreenVisibility(int lockscreenVisibility) {
440        this.mLockscreenVisibility = lockscreenVisibility;
441    }
442
443    /**
444     * Returns the id of this channel.
445     */
446    public String getId() {
447        return mId;
448    }
449
450    /**
451     * Returns the user visible name of this channel.
452     */
453    public CharSequence getName() {
454        return mName;
455    }
456
457    /**
458     * Returns the user visible description of this channel.
459     */
460    public String getDescription() {
461        return mDesc;
462    }
463
464    /**
465     * Returns the user specified importance e.g. {@link NotificationManager#IMPORTANCE_LOW} for
466     * notifications posted to this channel. Note: This value might be >
467     * {@link NotificationManager#IMPORTANCE_NONE}, but notifications posted to this channel will
468     * not be shown to the user if the parent {@link NotificationChannelGroup} or app is blocked.
469     * See {@link NotificationChannelGroup#isBlocked()} and
470     * {@link NotificationManager#areNotificationsEnabled()}.
471     */
472    public int getImportance() {
473        return mImportance;
474    }
475
476    /**
477     * Whether or not notifications posted to this channel can bypass the Do Not Disturb
478     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
479     */
480    public boolean canBypassDnd() {
481        return mBypassDnd;
482    }
483
484    /**
485     * Returns the notification sound for this channel.
486     */
487    public Uri getSound() {
488        return mSound;
489    }
490
491    /**
492     * Returns the audio attributes for sound played by notifications posted to this channel.
493     */
494    public AudioAttributes getAudioAttributes() {
495        return mAudioAttributes;
496    }
497
498    /**
499     * Returns whether notifications posted to this channel trigger notification lights.
500     */
501    public boolean shouldShowLights() {
502        return mLights;
503    }
504
505    /**
506     * Returns the notification light color for notifications posted to this channel. Irrelevant
507     * unless {@link #shouldShowLights()}.
508     */
509    public int getLightColor() {
510        return mLightColor;
511    }
512
513    /**
514     * Returns whether notifications posted to this channel always vibrate.
515     */
516    public boolean shouldVibrate() {
517        return mVibrationEnabled;
518    }
519
520    /**
521     * Returns the vibration pattern for notifications posted to this channel. Will be ignored if
522     * vibration is not enabled ({@link #shouldVibrate()}.
523     */
524    public long[] getVibrationPattern() {
525        return mVibration;
526    }
527
528    /**
529     * Returns whether or not notifications posted to this channel are shown on the lockscreen in
530     * full or redacted form.
531     */
532    public int getLockscreenVisibility() {
533        return mLockscreenVisibility;
534    }
535
536    /**
537     * Returns whether notifications posted to this channel can appear as badges in a Launcher
538     * application.
539     *
540     * Note that badging may be disabled for other reasons.
541     */
542    public boolean canShowBadge() {
543        return mShowBadge;
544    }
545
546    /**
547     * Returns what group this channel belongs to.
548     *
549     * This is used only for visually grouping channels in the UI.
550     */
551    public String getGroup() {
552        return mGroup;
553    }
554
555    /**
556     * @hide
557     */
558    @SystemApi
559    public boolean isDeleted() {
560        return mDeleted;
561    }
562
563    /**
564     * @hide
565     */
566    @SystemApi
567    public int getUserLockedFields() {
568        return mUserLockedFields;
569    }
570
571    /**
572     * @hide
573     */
574    public boolean isBlockableSystem() {
575        return mBlockableSystem;
576    }
577
578    /**
579     * @hide
580     */
581    public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
582        populateFromXml(parser, true, context);
583    }
584
585    /**
586     * @hide
587     */
588    @SystemApi
589    public void populateFromXml(XmlPullParser parser) {
590        populateFromXml(parser, false, null);
591    }
592
593    /**
594     * If {@param forRestore} is true, {@param Context} MUST be non-null.
595     */
596    private void populateFromXml(XmlPullParser parser, boolean forRestore,
597            @Nullable Context context) {
598        Preconditions.checkArgument(!forRestore || context != null,
599                "forRestore is true but got null context");
600
601        // Name, id, and importance are set in the constructor.
602        setDescription(parser.getAttributeValue(null, ATT_DESC));
603        setBypassDnd(Notification.PRIORITY_DEFAULT
604                != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
605        setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
606
607        Uri sound = safeUri(parser, ATT_SOUND);
608        setSound(forRestore ? restoreSoundUri(context, sound) : sound, safeAudioAttributes(parser));
609
610        enableLights(safeBool(parser, ATT_LIGHTS, false));
611        setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
612        setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
613        enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
614        setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
615        setDeleted(safeBool(parser, ATT_DELETED, false));
616        setGroup(parser.getAttributeValue(null, ATT_GROUP));
617        lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
618        setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
619    }
620
621    @Nullable
622    private Uri restoreSoundUri(Context context, @Nullable Uri uri) {
623        if (uri == null) {
624            return null;
625        }
626        ContentResolver contentResolver = context.getContentResolver();
627        // There are backups out there with uncanonical uris (because we fixed this after
628        // shipping). If uncanonical uris are given to MediaProvider.uncanonicalize it won't
629        // verify the uri against device storage and we'll possibly end up with a broken uri.
630        // We then canonicalize the uri to uncanonicalize it back, which means we properly check
631        // the uri and in the case of not having the resource we end up with the default - better
632        // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
633        // according to the docs because canonicalize method has to handle canonical uris as well.
634        Uri canonicalizedUri = contentResolver.canonicalize(uri);
635        if (canonicalizedUri == null) {
636            // We got a null because the uri in the backup does not exist here, so we return default
637            return Settings.System.DEFAULT_NOTIFICATION_URI;
638        }
639        return contentResolver.uncanonicalize(canonicalizedUri);
640    }
641
642    /**
643     * @hide
644     */
645    @SystemApi
646    public void writeXml(XmlSerializer out) throws IOException {
647        writeXml(out, false, null);
648    }
649
650    /**
651     * @hide
652     */
653    public void writeXmlForBackup(XmlSerializer out, Context context) throws IOException {
654        writeXml(out, true, context);
655    }
656
657    private Uri getSoundForBackup(Context context) {
658        Uri sound = getSound();
659        if (sound == null) {
660            return null;
661        }
662        Uri canonicalSound = context.getContentResolver().canonicalize(sound);
663        if (canonicalSound == null) {
664            // The content provider does not support canonical uris so we backup the default
665            return Settings.System.DEFAULT_NOTIFICATION_URI;
666        }
667        return canonicalSound;
668    }
669
670    /**
671     * If {@param forBackup} is true, {@param Context} MUST be non-null.
672     */
673    private void writeXml(XmlSerializer out, boolean forBackup, @Nullable Context context)
674            throws IOException {
675        Preconditions.checkArgument(!forBackup || context != null,
676                "forBackup is true but got null context");
677        out.startTag(null, TAG_CHANNEL);
678        out.attribute(null, ATT_ID, getId());
679        if (getName() != null) {
680            out.attribute(null, ATT_NAME, getName().toString());
681        }
682        if (getDescription() != null) {
683            out.attribute(null, ATT_DESC, getDescription());
684        }
685        if (getImportance() != DEFAULT_IMPORTANCE) {
686            out.attribute(
687                    null, ATT_IMPORTANCE, Integer.toString(getImportance()));
688        }
689        if (canBypassDnd()) {
690            out.attribute(
691                    null, ATT_PRIORITY, Integer.toString(Notification.PRIORITY_MAX));
692        }
693        if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
694            out.attribute(null, ATT_VISIBILITY,
695                    Integer.toString(getLockscreenVisibility()));
696        }
697        Uri sound = forBackup ? getSoundForBackup(context) : getSound();
698        if (sound != null) {
699            out.attribute(null, ATT_SOUND, sound.toString());
700        }
701        if (getAudioAttributes() != null) {
702            out.attribute(null, ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
703            out.attribute(null, ATT_CONTENT_TYPE,
704                    Integer.toString(getAudioAttributes().getContentType()));
705            out.attribute(null, ATT_FLAGS, Integer.toString(getAudioAttributes().getFlags()));
706        }
707        if (shouldShowLights()) {
708            out.attribute(null, ATT_LIGHTS, Boolean.toString(shouldShowLights()));
709        }
710        if (getLightColor() != DEFAULT_LIGHT_COLOR) {
711            out.attribute(null, ATT_LIGHT_COLOR, Integer.toString(getLightColor()));
712        }
713        if (shouldVibrate()) {
714            out.attribute(null, ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
715        }
716        if (getVibrationPattern() != null) {
717            out.attribute(null, ATT_VIBRATION, longArrayToString(getVibrationPattern()));
718        }
719        if (getUserLockedFields() != 0) {
720            out.attribute(null, ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
721        }
722        if (canShowBadge()) {
723            out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
724        }
725        if (isDeleted()) {
726            out.attribute(null, ATT_DELETED, Boolean.toString(isDeleted()));
727        }
728        if (getGroup() != null) {
729            out.attribute(null, ATT_GROUP, getGroup());
730        }
731        if (isBlockableSystem()) {
732            out.attribute(null, ATT_BLOCKABLE_SYSTEM, Boolean.toString(isBlockableSystem()));
733        }
734
735        out.endTag(null, TAG_CHANNEL);
736    }
737
738    /**
739     * @hide
740     */
741    @SystemApi
742    public JSONObject toJson() throws JSONException {
743        JSONObject record = new JSONObject();
744        record.put(ATT_ID, getId());
745        record.put(ATT_NAME, getName());
746        record.put(ATT_DESC, getDescription());
747        if (getImportance() != DEFAULT_IMPORTANCE) {
748            record.put(ATT_IMPORTANCE,
749                    NotificationListenerService.Ranking.importanceToString(getImportance()));
750        }
751        if (canBypassDnd()) {
752            record.put(ATT_PRIORITY, Notification.PRIORITY_MAX);
753        }
754        if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
755            record.put(ATT_VISIBILITY, Notification.visibilityToString(getLockscreenVisibility()));
756        }
757        if (getSound() != null) {
758            record.put(ATT_SOUND, getSound().toString());
759        }
760        if (getAudioAttributes() != null) {
761            record.put(ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
762            record.put(ATT_CONTENT_TYPE,
763                    Integer.toString(getAudioAttributes().getContentType()));
764            record.put(ATT_FLAGS, Integer.toString(getAudioAttributes().getFlags()));
765        }
766        record.put(ATT_LIGHTS, Boolean.toString(shouldShowLights()));
767        record.put(ATT_LIGHT_COLOR, Integer.toString(getLightColor()));
768        record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
769        record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
770        record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
771        record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
772        record.put(ATT_DELETED, Boolean.toString(isDeleted()));
773        record.put(ATT_GROUP, getGroup());
774        record.put(ATT_BLOCKABLE_SYSTEM, isBlockableSystem());
775        return record;
776    }
777
778    private static AudioAttributes safeAudioAttributes(XmlPullParser parser) {
779        int usage = safeInt(parser, ATT_USAGE, AudioAttributes.USAGE_NOTIFICATION);
780        int contentType = safeInt(parser, ATT_CONTENT_TYPE,
781                AudioAttributes.CONTENT_TYPE_SONIFICATION);
782        int flags = safeInt(parser, ATT_FLAGS, 0);
783        return new AudioAttributes.Builder()
784                .setUsage(usage)
785                .setContentType(contentType)
786                .setFlags(flags)
787                .build();
788    }
789
790    private static Uri safeUri(XmlPullParser parser, String att) {
791        final String val = parser.getAttributeValue(null, att);
792        return val == null ? null : Uri.parse(val);
793    }
794
795    private static int safeInt(XmlPullParser parser, String att, int defValue) {
796        final String val = parser.getAttributeValue(null, att);
797        return tryParseInt(val, defValue);
798    }
799
800    private static int tryParseInt(String value, int defValue) {
801        if (TextUtils.isEmpty(value)) return defValue;
802        try {
803            return Integer.parseInt(value);
804        } catch (NumberFormatException e) {
805            return defValue;
806        }
807    }
808
809    private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
810        final String value = parser.getAttributeValue(null, att);
811        if (TextUtils.isEmpty(value)) return defValue;
812        return Boolean.parseBoolean(value);
813    }
814
815    private static long[] safeLongArray(XmlPullParser parser, String att, long[] defValue) {
816        final String attributeValue = parser.getAttributeValue(null, att);
817        if (TextUtils.isEmpty(attributeValue)) return defValue;
818        String[] values = attributeValue.split(DELIMITER);
819        long[] longValues = new long[values.length];
820        for (int i = 0; i < values.length; i++) {
821            try {
822                longValues[i] = Long.parseLong(values[i]);
823            } catch (NumberFormatException e) {
824                longValues[i] = 0;
825            }
826        }
827        return longValues;
828    }
829
830    private static String longArrayToString(long[] values) {
831        StringBuffer sb = new StringBuffer();
832        if (values != null && values.length > 0) {
833            for (int i = 0; i < values.length - 1; i++) {
834                sb.append(values[i]).append(DELIMITER);
835            }
836            sb.append(values[values.length - 1]);
837        }
838        return sb.toString();
839    }
840
841    public static final Creator<NotificationChannel> CREATOR = new Creator<NotificationChannel>() {
842        @Override
843        public NotificationChannel createFromParcel(Parcel in) {
844            return new NotificationChannel(in);
845        }
846
847        @Override
848        public NotificationChannel[] newArray(int size) {
849            return new NotificationChannel[size];
850        }
851    };
852
853    @Override
854    public int describeContents() {
855        return 0;
856    }
857
858    @Override
859    public boolean equals(Object o) {
860        if (this == o) return true;
861        if (o == null || getClass() != o.getClass()) return false;
862
863        NotificationChannel that = (NotificationChannel) o;
864
865        if (getImportance() != that.getImportance()) return false;
866        if (mBypassDnd != that.mBypassDnd) return false;
867        if (getLockscreenVisibility() != that.getLockscreenVisibility()) return false;
868        if (mLights != that.mLights) return false;
869        if (getLightColor() != that.getLightColor()) return false;
870        if (getUserLockedFields() != that.getUserLockedFields()) return false;
871        if (mVibrationEnabled != that.mVibrationEnabled) return false;
872        if (mShowBadge != that.mShowBadge) return false;
873        if (isDeleted() != that.isDeleted()) return false;
874        if (isBlockableSystem() != that.isBlockableSystem()) return false;
875        if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false;
876        if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) {
877            return false;
878        }
879        if (getDescription() != null ? !getDescription().equals(that.getDescription())
880                : that.getDescription() != null) {
881            return false;
882        }
883        if (getSound() != null ? !getSound().equals(that.getSound()) : that.getSound() != null) {
884            return false;
885        }
886        if (!Arrays.equals(mVibration, that.mVibration)) return false;
887        if (getGroup() != null ? !getGroup().equals(that.getGroup()) : that.getGroup() != null) {
888            return false;
889        }
890        return getAudioAttributes() != null ? getAudioAttributes().equals(that.getAudioAttributes())
891                : that.getAudioAttributes() == null;
892
893    }
894
895    @Override
896    public int hashCode() {
897        int result = getId() != null ? getId().hashCode() : 0;
898        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
899        result = 31 * result + (getDescription() != null ? getDescription().hashCode() : 0);
900        result = 31 * result + getImportance();
901        result = 31 * result + (mBypassDnd ? 1 : 0);
902        result = 31 * result + getLockscreenVisibility();
903        result = 31 * result + (getSound() != null ? getSound().hashCode() : 0);
904        result = 31 * result + (mLights ? 1 : 0);
905        result = 31 * result + getLightColor();
906        result = 31 * result + Arrays.hashCode(mVibration);
907        result = 31 * result + getUserLockedFields();
908        result = 31 * result + (mVibrationEnabled ? 1 : 0);
909        result = 31 * result + (mShowBadge ? 1 : 0);
910        result = 31 * result + (isDeleted() ? 1 : 0);
911        result = 31 * result + (getGroup() != null ? getGroup().hashCode() : 0);
912        result = 31 * result + (getAudioAttributes() != null ? getAudioAttributes().hashCode() : 0);
913        result = 31 * result + (isBlockableSystem() ? 1 : 0);
914        return result;
915    }
916
917    @Override
918    public String toString() {
919        return "NotificationChannel{"
920                + "mId='" + mId + '\''
921                + ", mName=" + mName
922                + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
923                + ", mImportance=" + mImportance
924                + ", mBypassDnd=" + mBypassDnd
925                + ", mLockscreenVisibility=" + mLockscreenVisibility
926                + ", mSound=" + mSound
927                + ", mLights=" + mLights
928                + ", mLightColor=" + mLightColor
929                + ", mVibration=" + Arrays.toString(mVibration)
930                + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
931                + ", mVibrationEnabled=" + mVibrationEnabled
932                + ", mShowBadge=" + mShowBadge
933                + ", mDeleted=" + mDeleted
934                + ", mGroup='" + mGroup + '\''
935                + ", mAudioAttributes=" + mAudioAttributes
936                + ", mBlockableSystem=" + mBlockableSystem
937                + '}';
938    }
939
940    /** @hide */
941    public void writeToProto(ProtoOutputStream proto, long fieldId) {
942        final long token = proto.start(fieldId);
943
944        proto.write(NotificationChannelProto.ID, mId);
945        proto.write(NotificationChannelProto.NAME, mName);
946        proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
947        proto.write(NotificationChannelProto.IMPORTANCE, mImportance);
948        proto.write(NotificationChannelProto.CAN_BYPASS_DND, mBypassDnd);
949        proto.write(NotificationChannelProto.LOCKSCREEN_VISIBILITY, mLockscreenVisibility);
950        if (mSound != null) {
951            proto.write(NotificationChannelProto.SOUND, mSound.toString());
952        }
953        proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
954        proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
955        if (mVibration != null) {
956            for (long v : mVibration) {
957                proto.write(NotificationChannelProto.VIBRATION, v);
958            }
959        }
960        proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
961        proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
962        proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
963        proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
964        proto.write(NotificationChannelProto.GROUP, mGroup);
965        if (mAudioAttributes != null) {
966            mAudioAttributes.writeToProto(proto, NotificationChannelProto.AUDIO_ATTRIBUTES);
967        }
968        proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
969
970        proto.end(token);
971    }
972}
973