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