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