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.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.SystemApi;
21 import android.annotation.TestApi;
22 import android.app.NotificationManager.Importance;
23 import android.compat.annotation.UnsupportedAppUsage;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ShortcutInfo;
28 import android.media.AudioAttributes;
29 import android.media.RingtoneManager;
30 import android.net.Uri;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.provider.Settings;
34 import android.service.notification.NotificationListenerService;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.util.proto.ProtoOutputStream;
38 
39 import com.android.internal.util.Preconditions;
40 import com.android.internal.util.XmlUtils;
41 import com.android.modules.utils.TypedXmlPullParser;
42 import com.android.modules.utils.TypedXmlSerializer;
43 
44 import org.json.JSONException;
45 import org.json.JSONObject;
46 import org.xmlpull.v1.XmlPullParser;
47 import org.xmlpull.v1.XmlSerializer;
48 
49 import java.io.FileNotFoundException;
50 import java.io.IOException;
51 import java.io.PrintWriter;
52 import java.util.Arrays;
53 import java.util.Objects;
54 
55 /**
56  * A representation of settings that apply to a collection of similarly themed notifications.
57  */
58 public final class NotificationChannel implements Parcelable {
59     private static final String TAG = "NotificationChannel";
60 
61     /**
62      * The id of the default channel for an app. This id is reserved by the system. All
63      * notifications posted from apps targeting {@link android.os.Build.VERSION_CODES#N_MR1} or
64      * earlier without a notification channel specified are posted to this channel.
65      */
66     public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
67 
68     /**
69      * The formatter used by the system to create an id for notification
70      * channels when it automatically creates conversation channels on behalf of an app. The format
71      * string takes two arguments, in this order: the
72      * {@link #getId()} of the original notification channel, and the
73      * {@link ShortcutInfo#getId() id} of the conversation.
74      * @hide
75      */
76     public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
77 
78     /**
79      * TODO: STOPSHIP  remove
80      * Conversation id to use for apps that aren't providing them yet.
81      * @hide
82      */
83     public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
84 
85     /**
86      * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
87      * that have to do with editing sound, like a tone picker
88      * ({@link #setSound(Uri, AudioAttributes)}).
89      */
90     public static final String EDIT_SOUND = "sound";
91     /**
92      * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
93      * that have to do with editing vibration ({@link #enableVibration(boolean)},
94      * {@link #setVibrationPattern(long[])}).
95      */
96     public static final String EDIT_VIBRATION = "vibration";
97     /**
98      * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
99      * that have to do with editing importance ({@link #setImportance(int)}) and/or conversation
100      * priority.
101      */
102     public static final String EDIT_IMPORTANCE = "importance";
103     /**
104      * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
105      * that have to do with editing behavior on devices that are locked or have a turned off
106      * display ({@link #setLockscreenVisibility(int)}, {@link #enableLights(boolean)},
107      * {@link #setLightColor(int)}).
108      */
109     public static final String EDIT_LOCKED_DEVICE = "locked";
110     /**
111      * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
112      * that have to do with editing do not disturb bypass {(@link #setBypassDnd(boolean)}) .
113      */
114     public static final String EDIT_ZEN = "zen";
115     /**
116      * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
117      * that have to do with editing conversation settings (demoting or restoring a channel to
118      * be a Conversation, changing bubble behavior, or setting the priority of a conversation).
119      */
120     public static final String EDIT_CONVERSATION = "conversation";
121     /**
122      * Extra value for {@link Settings#EXTRA_CHANNEL_FILTER_LIST}. Include to show fields
123      * that have to do with editing launcher behavior (showing badges)}.
124      */
125     public static final String EDIT_LAUNCHER = "launcher";
126 
127     /**
128      * The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
129      * limit.
130      * @hide
131      */
132     public static final int MAX_TEXT_LENGTH = 1000;
133     /**
134      * @hide
135      */
136     public static final int MAX_VIBRATION_LENGTH = 1000;
137 
138     private static final String TAG_CHANNEL = "channel";
139     private static final String ATT_NAME = "name";
140     private static final String ATT_DESC = "desc";
141     private static final String ATT_ID = "id";
142     private static final String ATT_DELETED = "deleted";
143     private static final String ATT_PRIORITY = "priority";
144     private static final String ATT_VISIBILITY = "visibility";
145     private static final String ATT_IMPORTANCE = "importance";
146     private static final String ATT_LIGHTS = "lights";
147     private static final String ATT_LIGHT_COLOR = "light_color";
148     private static final String ATT_VIBRATION = "vibration";
149     private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
150     private static final String ATT_SOUND = "sound";
151     private static final String ATT_USAGE = "usage";
152     private static final String ATT_FLAGS = "flags";
153     private static final String ATT_CONTENT_TYPE = "content_type";
154     private static final String ATT_SHOW_BADGE = "show_badge";
155     private static final String ATT_USER_LOCKED = "locked";
156     /**
157      * This attribute represents both foreground services and user initiated jobs in U+.
158      * It was not renamed in U on purpose, in order to avoid creating an unnecessary migration path.
159      */
160     private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
161     private static final String ATT_GROUP = "group";
162     private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
163     private static final String ATT_ALLOW_BUBBLE = "allow_bubbles";
164     private static final String ATT_ORIG_IMP = "orig_imp";
165     private static final String ATT_PARENT_CHANNEL = "parent";
166     private static final String ATT_CONVERSATION_ID = "conv_id";
167     private static final String ATT_IMP_CONVERSATION = "imp_conv";
168     private static final String ATT_DEMOTE = "dem";
169     private static final String ATT_DELETED_TIME_MS = "del_time";
170     private static final String DELIMITER = ",";
171 
172     /**
173      * @hide
174      */
175     public static final int USER_LOCKED_PRIORITY = 0x00000001;
176     /**
177      * @hide
178      */
179     public static final int USER_LOCKED_VISIBILITY = 0x00000002;
180     /**
181      * @hide
182      */
183     public static final int USER_LOCKED_IMPORTANCE = 0x00000004;
184     /**
185      * @hide
186      */
187     public static final int USER_LOCKED_LIGHTS = 0x00000008;
188     /**
189      * @hide
190      */
191     public static final int USER_LOCKED_VIBRATION = 0x00000010;
192     /**
193      * @hide
194      */
195     @SystemApi
196     public static final int USER_LOCKED_SOUND = 0x00000020;
197 
198     /**
199      * @hide
200      */
201     public static final int USER_LOCKED_SHOW_BADGE = 0x00000080;
202 
203     /**
204      * @hide
205      */
206     public static final int USER_LOCKED_ALLOW_BUBBLE = 0x00000100;
207 
208     /**
209      * @hide
210      */
211     public static final int[] LOCKABLE_FIELDS = new int[] {
212             USER_LOCKED_PRIORITY,
213             USER_LOCKED_VISIBILITY,
214             USER_LOCKED_IMPORTANCE,
215             USER_LOCKED_LIGHTS,
216             USER_LOCKED_VIBRATION,
217             USER_LOCKED_SOUND,
218             USER_LOCKED_SHOW_BADGE,
219             USER_LOCKED_ALLOW_BUBBLE
220     };
221 
222     /**
223      * @hide
224      */
225     public static final int DEFAULT_ALLOW_BUBBLE = -1;
226     /**
227      * @hide
228      */
229     public static final int ALLOW_BUBBLE_ON = 1;
230     /**
231      * @hide
232      */
233     public static final int ALLOW_BUBBLE_OFF = 0;
234 
235     private static final int DEFAULT_LIGHT_COLOR = 0;
236     private static final int DEFAULT_VISIBILITY =
237             NotificationManager.VISIBILITY_NO_OVERRIDE;
238     private static final int DEFAULT_IMPORTANCE =
239             NotificationManager.IMPORTANCE_UNSPECIFIED;
240     private static final boolean DEFAULT_DELETED = false;
241     private static final boolean DEFAULT_SHOW_BADGE = true;
242     private static final long DEFAULT_DELETION_TIME_MS = -1;
243 
244     @UnsupportedAppUsage
245     private String mId;
246     private String mName;
247     private String mDesc;
248     private int mImportance = DEFAULT_IMPORTANCE;
249     private int mOriginalImportance = DEFAULT_IMPORTANCE;
250     private boolean mBypassDnd;
251     private int mLockscreenVisibility = DEFAULT_VISIBILITY;
252     private Uri mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
253     private boolean mSoundRestored = false;
254     private boolean mLights;
255     private int mLightColor = DEFAULT_LIGHT_COLOR;
256     private long[] mVibration;
257     // Bitwise representation of fields that have been changed by the user, preventing the app from
258     // making changes to these fields.
259     private int mUserLockedFields;
260     private boolean mUserVisibleTaskShown;
261     private boolean mVibrationEnabled;
262     private boolean mShowBadge = DEFAULT_SHOW_BADGE;
263     private boolean mDeleted = DEFAULT_DELETED;
264     private String mGroup;
265     private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
266     // If this is a blockable system notification channel.
267     private boolean mBlockableSystem = false;
268     private int mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
269     private boolean mImportanceLockedDefaultApp;
270     private String mParentId = null;
271     private String mConversationId = null;
272     private boolean mDemoted = false;
273     private boolean mImportantConvo = false;
274     private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
275 
276     /**
277      * Creates a notification channel.
278      *
279      * @param id The id of the channel. Must be unique per package. The value may be truncated if
280      *           it is too long.
281      * @param name The user visible name of the channel. You can rename this channel when the system
282      *             locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED}
283      *             broadcast. The recommended maximum length is 40 characters; the value may be
284      *             truncated if it is too long.
285      * @param importance The importance of the channel. This controls how interruptive notifications
286      *                   posted to this channel are.
287      */
NotificationChannel(String id, CharSequence name, @Importance int importance)288     public NotificationChannel(String id, CharSequence name, @Importance int importance) {
289         this.mId = getTrimmedString(id);
290         this.mName = name != null ? getTrimmedString(name.toString()) : null;
291         this.mImportance = importance;
292     }
293 
294     /**
295      * @hide
296      */
NotificationChannel(Parcel in)297     protected NotificationChannel(Parcel in) {
298         if (in.readByte() != 0) {
299             mId = getTrimmedString(in.readString());
300         } else {
301             mId = null;
302         }
303         if (in.readByte() != 0) {
304             mName = getTrimmedString(in.readString());
305         } else {
306             mName = null;
307         }
308         if (in.readByte() != 0) {
309             mDesc = getTrimmedString(in.readString());
310         } else {
311             mDesc = null;
312         }
313         mImportance = in.readInt();
314         mBypassDnd = in.readByte() != 0;
315         mLockscreenVisibility = in.readInt();
316         if (in.readByte() != 0) {
317             mSound = Uri.CREATOR.createFromParcel(in);
318             mSound = Uri.parse(getTrimmedString(mSound.toString()));
319         } else {
320             mSound = null;
321         }
322         mLights = in.readByte() != 0;
323         mVibration = in.createLongArray();
324         if (mVibration != null && mVibration.length > MAX_VIBRATION_LENGTH) {
325             mVibration = Arrays.copyOf(mVibration, MAX_VIBRATION_LENGTH);
326         }
327         mUserLockedFields = in.readInt();
328         mUserVisibleTaskShown = in.readByte() != 0;
329         mVibrationEnabled = in.readByte() != 0;
330         mShowBadge = in.readByte() != 0;
331         mDeleted = in.readByte() != 0;
332         if (in.readByte() != 0) {
333             mGroup = getTrimmedString(in.readString());
334         } else {
335             mGroup = null;
336         }
337         mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null;
338         mLightColor = in.readInt();
339         mBlockableSystem = in.readBoolean();
340         mAllowBubbles = in.readInt();
341         mOriginalImportance = in.readInt();
342         mParentId = getTrimmedString(in.readString());
343         mConversationId = getTrimmedString(in.readString());
344         mDemoted = in.readBoolean();
345         mImportantConvo = in.readBoolean();
346         mDeletedTime = in.readLong();
347         mImportanceLockedDefaultApp = in.readBoolean();
348     }
349 
350     @Override
writeToParcel(Parcel dest, int flags)351     public void writeToParcel(Parcel dest, int flags) {
352         if (mId != null) {
353             dest.writeByte((byte) 1);
354             dest.writeString(mId);
355         } else {
356             dest.writeByte((byte) 0);
357         }
358         if (mName != null) {
359             dest.writeByte((byte) 1);
360             dest.writeString(mName);
361         } else {
362             dest.writeByte((byte) 0);
363         }
364         if (mDesc != null) {
365             dest.writeByte((byte) 1);
366             dest.writeString(mDesc);
367         } else {
368             dest.writeByte((byte) 0);
369         }
370         dest.writeInt(mImportance);
371         dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0);
372         dest.writeInt(mLockscreenVisibility);
373         if (mSound != null) {
374             dest.writeByte((byte) 1);
375             mSound.writeToParcel(dest, 0);
376         } else {
377             dest.writeByte((byte) 0);
378         }
379         dest.writeByte(mLights ? (byte) 1 : (byte) 0);
380         dest.writeLongArray(mVibration);
381         dest.writeInt(mUserLockedFields);
382         dest.writeByte(mUserVisibleTaskShown ? (byte) 1 : (byte) 0);
383         dest.writeByte(mVibrationEnabled ? (byte) 1 : (byte) 0);
384         dest.writeByte(mShowBadge ? (byte) 1 : (byte) 0);
385         dest.writeByte(mDeleted ? (byte) 1 : (byte) 0);
386         if (mGroup != null) {
387             dest.writeByte((byte) 1);
388             dest.writeString(mGroup);
389         } else {
390             dest.writeByte((byte) 0);
391         }
392         if (mAudioAttributes != null) {
393             dest.writeInt(1);
394             mAudioAttributes.writeToParcel(dest, 0);
395         } else {
396             dest.writeInt(0);
397         }
398         dest.writeInt(mLightColor);
399         dest.writeBoolean(mBlockableSystem);
400         dest.writeInt(mAllowBubbles);
401         dest.writeInt(mOriginalImportance);
402         dest.writeString(mParentId);
403         dest.writeString(mConversationId);
404         dest.writeBoolean(mDemoted);
405         dest.writeBoolean(mImportantConvo);
406         dest.writeLong(mDeletedTime);
407         dest.writeBoolean(mImportanceLockedDefaultApp);
408     }
409 
410     /**
411      * @hide
412      */
413     @TestApi
lockFields(int field)414     public void lockFields(int field) {
415         mUserLockedFields |= field;
416     }
417 
418     /**
419      * @hide
420      */
unlockFields(int field)421     public void unlockFields(int field) {
422         mUserLockedFields &= ~field;
423     }
424 
425     /**
426      * @hide
427      */
428     @TestApi
setUserVisibleTaskShown(boolean shown)429     public void setUserVisibleTaskShown(boolean shown) {
430         mUserVisibleTaskShown = shown;
431     }
432 
433     /**
434      * @hide
435      */
436     @TestApi
setDeleted(boolean deleted)437     public void setDeleted(boolean deleted) {
438         mDeleted = deleted;
439     }
440 
441     /**
442      * @hide
443      */
444     @TestApi
setDeletedTimeMs(long time)445     public void setDeletedTimeMs(long time) {
446         mDeletedTime = time;
447     }
448 
449     /**
450      * @hide
451      */
452     @TestApi
setImportantConversation(boolean importantConvo)453     public void setImportantConversation(boolean importantConvo) {
454         mImportantConvo = importantConvo;
455     }
456 
457     /**
458      * Allows users to block notifications sent through this channel, if this channel belongs to
459      * a package that otherwise would have notifications "fixed" as enabled.
460      *
461      * If the channel does not belong to a package that has a fixed notification permission, this
462      * method does nothing, since such channels are blockable by default and cannot be set to be
463      * unblockable.
464      * @param blockable if {@code true}, allows users to block notifications on this channel.
465      */
setBlockable(boolean blockable)466     public void setBlockable(boolean blockable) {
467         mBlockableSystem = blockable;
468     }
469     // Modifiable by apps post channel creation
470 
471     /**
472      * Sets the user visible name of this channel.
473      *
474      * <p>The recommended maximum length is 40 characters; the value may be truncated if it is too
475      * long.
476      */
setName(CharSequence name)477     public void setName(CharSequence name) {
478         mName = name != null ? getTrimmedString(name.toString()) : null;
479     }
480 
481     /**
482      * Sets the user visible description of this channel.
483      *
484      * <p>The recommended maximum length is 300 characters; the value may be truncated if it is too
485      * long.
486      */
setDescription(String description)487     public void setDescription(String description) {
488         mDesc = getTrimmedString(description);
489     }
490 
getTrimmedString(String input)491     private String getTrimmedString(String input) {
492         if (input != null && input.length() > MAX_TEXT_LENGTH) {
493             return input.substring(0, MAX_TEXT_LENGTH);
494         }
495         return input;
496     }
497 
498     /**
499      * @hide
500      */
setId(String id)501     public void setId(String id) {
502         mId = id;
503     }
504 
505     // Modifiable by apps on channel creation.
506 
507     /**
508      * Sets what group this channel belongs to.
509      *
510      * Group information is only used for presentation, not for behavior.
511      *
512      * Only modifiable before the channel is submitted to
513      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}, unless the
514      * channel is not currently part of a group.
515      *
516      * @param groupId the id of a group created by
517      * {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.
518      */
setGroup(String groupId)519     public void setGroup(String groupId) {
520         this.mGroup = groupId;
521     }
522 
523     /**
524      * Sets whether notifications posted to this channel can appear as application icon badges
525      * in a Launcher.
526      *
527      * Only modifiable before the channel is submitted to
528      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
529      *
530      * @param showBadge true if badges should be allowed to be shown.
531      */
setShowBadge(boolean showBadge)532     public void setShowBadge(boolean showBadge) {
533         this.mShowBadge = showBadge;
534     }
535 
536     /**
537      * Sets the sound that should be played for notifications posted to this channel and its
538      * audio attributes. Notification channels with an {@link #getImportance() importance} of at
539      * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
540      *
541      * Only modifiable before the channel is submitted to
542      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
543      */
setSound(Uri sound, AudioAttributes audioAttributes)544     public void setSound(Uri sound, AudioAttributes audioAttributes) {
545         this.mSound = sound;
546         this.mAudioAttributes = audioAttributes;
547     }
548 
549     /**
550      * Sets whether notifications posted to this channel should display notification lights,
551      * on devices that support that feature.
552      *
553      * Only modifiable before the channel is submitted to
554      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
555      */
enableLights(boolean lights)556     public void enableLights(boolean lights) {
557         this.mLights = lights;
558     }
559 
560     /**
561      * Sets the notification light color for notifications posted to this channel, if lights are
562      * {@link #enableLights(boolean) enabled} on this channel and the device supports that feature.
563      *
564      * Only modifiable before the channel is submitted to
565      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
566      */
setLightColor(int argb)567     public void setLightColor(int argb) {
568         this.mLightColor = argb;
569     }
570 
571     /**
572      * Sets whether notification posted to this channel should vibrate. The vibration pattern can
573      * be set with {@link #setVibrationPattern(long[])}.
574      *
575      * Only modifiable before the channel is submitted to
576      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
577      */
enableVibration(boolean vibration)578     public void enableVibration(boolean vibration) {
579         this.mVibrationEnabled = vibration;
580     }
581 
582     /**
583      * Sets the vibration pattern for notifications posted to this channel. If the provided
584      * pattern is valid (non-null, non-empty), will enable vibration on this channel
585      * (equivalent to calling {@link #enableVibration(boolean)} with {@code true}).
586      * Otherwise, vibration will be disabled unless {@link #enableVibration(boolean)} is
587      * used with {@code true}, in which case the default vibration will be used.
588      *
589      * Only modifiable before the channel is submitted to
590      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
591      */
setVibrationPattern(long[] vibrationPattern)592     public void setVibrationPattern(long[] vibrationPattern) {
593         this.mVibrationEnabled = vibrationPattern != null && vibrationPattern.length > 0;
594         this.mVibration = vibrationPattern;
595     }
596 
597     /**
598      * Sets the level of interruption of this notification channel.
599      *
600      * Only modifiable before the channel is submitted to
601      * {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
602      *
603      * @param importance the amount the user should be interrupted by
604      *            notifications from this channel.
605      */
setImportance(@mportance int importance)606     public void setImportance(@Importance int importance) {
607         this.mImportance = importance;
608     }
609 
610     // Modifiable by a notification ranker.
611 
612     /**
613      * Sets whether or not notifications posted to this channel can interrupt the user in
614      * {@link android.app.NotificationManager.Policy#INTERRUPTION_FILTER_PRIORITY} mode.
615      *
616      * Only modifiable by the system and notification ranker.
617      */
setBypassDnd(boolean bypassDnd)618     public void setBypassDnd(boolean bypassDnd) {
619         this.mBypassDnd = bypassDnd;
620     }
621 
622     /**
623      * Sets whether notifications posted to this channel appear on the lockscreen or not, and if so,
624      * whether they appear in a redacted form. See e.g. {@link Notification#VISIBILITY_SECRET}.
625      *
626      * Only modifiable by the system and notification ranker.
627      */
setLockscreenVisibility(int lockscreenVisibility)628     public void setLockscreenVisibility(int lockscreenVisibility) {
629         this.mLockscreenVisibility = lockscreenVisibility;
630     }
631 
632     /**
633      * As of Android 11 this value is no longer respected.
634      * @see #canBubble()
635      * @see Notification#getBubbleMetadata()
636      */
setAllowBubbles(boolean allowBubbles)637     public void setAllowBubbles(boolean allowBubbles) {
638         mAllowBubbles = allowBubbles ? ALLOW_BUBBLE_ON : ALLOW_BUBBLE_OFF;
639     }
640 
641     /**
642      * @hide
643      */
setAllowBubbles(int allowed)644     public void setAllowBubbles(int allowed) {
645         mAllowBubbles = allowed;
646     }
647 
648     /**
649      * Sets this channel as being converastion-centric. Different settings and functionality may be
650      * exposed for conversation-centric channels.
651      *
652      * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of
653      *                        this type would be posted to in absence of a specific conversation id.
654      *                        For example, if this channel represents 'Messages from Person A', the
655      *                        parent channel would be 'Messages.'
656      * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this
657      *                       channel's conversation.
658      */
setConversationId(@onNull String parentChannelId, @NonNull String conversationId)659     public void setConversationId(@NonNull String parentChannelId,
660             @NonNull String conversationId) {
661         mParentId = parentChannelId;
662         mConversationId = conversationId;
663     }
664 
665     /**
666      * Returns the id of this channel.
667      */
getId()668     public String getId() {
669         return mId;
670     }
671 
672     /**
673      * Returns the user visible name of this channel.
674      */
getName()675     public CharSequence getName() {
676         return mName;
677     }
678 
679     /**
680      * Returns the user visible description of this channel.
681      */
getDescription()682     public String getDescription() {
683         return mDesc;
684     }
685 
686     /**
687      * Returns the user specified importance e.g. {@link NotificationManager#IMPORTANCE_LOW} for
688      * notifications posted to this channel. Note: This value might be >
689      * {@link NotificationManager#IMPORTANCE_NONE}, but notifications posted to this channel will
690      * not be shown to the user if the parent {@link NotificationChannelGroup} or app is blocked.
691      * See {@link NotificationChannelGroup#isBlocked()} and
692      * {@link NotificationManager#areNotificationsEnabled()}.
693      */
getImportance()694     public int getImportance() {
695         return mImportance;
696     }
697 
698     /**
699      * Whether or not notifications posted to this channel can bypass the Do Not Disturb
700      * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY} mode.
701      */
canBypassDnd()702     public boolean canBypassDnd() {
703         return mBypassDnd;
704     }
705 
706     /**
707      * Whether or not this channel represents a conversation.
708      */
isConversation()709     public boolean isConversation() {
710         return !TextUtils.isEmpty(getConversationId());
711     }
712 
713 
714     /**
715      * Whether or not notifications in this conversation are considered important.
716      *
717      * <p>Important conversations may get special visual treatment, and might be able to bypass DND.
718      *
719      * <p>This is only valid for channels that represent conversations, that is,
720      * where {@link #isConversation()} is true.
721      */
isImportantConversation()722     public boolean isImportantConversation() {
723         return mImportantConvo;
724     }
725 
726     /**
727      * Returns the notification sound for this channel.
728      */
getSound()729     public Uri getSound() {
730         return mSound;
731     }
732 
733     /**
734      * Returns the audio attributes for sound played by notifications posted to this channel.
735      */
getAudioAttributes()736     public AudioAttributes getAudioAttributes() {
737         return mAudioAttributes;
738     }
739 
740     /**
741      * Returns whether notifications posted to this channel trigger notification lights.
742      */
shouldShowLights()743     public boolean shouldShowLights() {
744         return mLights;
745     }
746 
747     /**
748      * Returns the notification light color for notifications posted to this channel. Irrelevant
749      * unless {@link #shouldShowLights()}.
750      */
getLightColor()751     public int getLightColor() {
752         return mLightColor;
753     }
754 
755     /**
756      * Returns whether notifications posted to this channel always vibrate.
757      */
shouldVibrate()758     public boolean shouldVibrate() {
759         return mVibrationEnabled;
760     }
761 
762     /**
763      * Returns the vibration pattern for notifications posted to this channel. Will be ignored if
764      * vibration is not enabled ({@link #shouldVibrate()}).
765      */
getVibrationPattern()766     public long[] getVibrationPattern() {
767         return mVibration;
768     }
769 
770     /**
771      * Returns whether or not notifications posted to this channel are shown on the lockscreen in
772      * full or redacted form.
773      */
getLockscreenVisibility()774     public int getLockscreenVisibility() {
775         return mLockscreenVisibility;
776     }
777 
778     /**
779      * Returns whether notifications posted to this channel can appear as badges in a Launcher
780      * application.
781      *
782      * Note that badging may be disabled for other reasons.
783      */
canShowBadge()784     public boolean canShowBadge() {
785         return mShowBadge;
786     }
787 
788     /**
789      * Returns what group this channel belongs to.
790      *
791      * This is used only for visually grouping channels in the UI.
792      */
getGroup()793     public String getGroup() {
794         return mGroup;
795     }
796 
797     /**
798      * Returns whether notifications posted to this channel are allowed to display outside of the
799      * notification shade, in a floating window on top of other apps.
800      *
801      * @see Notification#getBubbleMetadata()
802      */
canBubble()803     public boolean canBubble() {
804         return mAllowBubbles == ALLOW_BUBBLE_ON;
805     }
806 
807     /**
808      * @hide
809      */
getAllowBubbles()810     public int getAllowBubbles() {
811         return mAllowBubbles;
812     }
813 
814     /**
815      * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's
816      * a conversation related channel. See {@link #setConversationId(String, String)}.
817      */
getParentChannelId()818     public @Nullable String getParentChannelId() {
819         return mParentId;
820     }
821 
822     /**
823      * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's
824      * associated with a conversation. See {@link #setConversationId(String, String)}.
825      */
getConversationId()826     public @Nullable String getConversationId() {
827         return mConversationId;
828     }
829 
830     /**
831      * @hide
832      */
833     @SystemApi
isDeleted()834     public boolean isDeleted() {
835         return mDeleted;
836     }
837 
838     /**
839      * @hide
840      */
getDeletedTimeMs()841     public long getDeletedTimeMs() {
842         return mDeletedTime;
843     }
844 
845     /**
846      * @hide
847      */
848     @SystemApi
getUserLockedFields()849     public int getUserLockedFields() {
850         return mUserLockedFields;
851     }
852 
853     /**
854      * @hide
855      */
isUserVisibleTaskShown()856     public boolean isUserVisibleTaskShown() {
857         return mUserVisibleTaskShown;
858     }
859 
860     /**
861      * Returns whether this channel is always blockable, even if the app is 'fixed' as
862      * non-blockable.
863      */
isBlockable()864     public boolean isBlockable() {
865         return mBlockableSystem;
866     }
867 
868     /**
869      * @hide
870      */
871     @TestApi
setImportanceLockedByCriticalDeviceFunction(boolean locked)872     public void setImportanceLockedByCriticalDeviceFunction(boolean locked) {
873         mImportanceLockedDefaultApp = locked;
874     }
875 
876     /**
877      * @hide
878      */
879     @TestApi
isImportanceLockedByCriticalDeviceFunction()880     public boolean isImportanceLockedByCriticalDeviceFunction() {
881         return mImportanceLockedDefaultApp;
882     }
883 
884     /**
885      * @hide
886      */
887     @TestApi
getOriginalImportance()888     public int getOriginalImportance() {
889         return mOriginalImportance;
890     }
891 
892     /**
893      * @hide
894      */
895     @TestApi
setOriginalImportance(int importance)896     public void setOriginalImportance(int importance) {
897         mOriginalImportance = importance;
898     }
899 
900     /**
901      * @hide
902      */
903     @TestApi
setDemoted(boolean demoted)904     public void setDemoted(boolean demoted) {
905         mDemoted = demoted;
906     }
907 
908     /**
909      * Returns whether the user has decided that this channel does not represent a conversation. The
910      * value will always be false for channels that never claimed to be conversations - that is,
911      * for channels where {@link #getConversationId()} and {@link #getParentChannelId()} are empty.
912      */
isDemoted()913     public boolean isDemoted() {
914         return mDemoted;
915     }
916 
917     /**
918      * Returns whether the user has chosen the importance of this channel, either to affirm the
919      * initial selection from the app, or changed it to be higher or lower.
920      * @see #getImportance()
921      */
hasUserSetImportance()922     public boolean hasUserSetImportance() {
923         return (mUserLockedFields & USER_LOCKED_IMPORTANCE) != 0;
924     }
925 
926     /**
927      * Returns whether the user has chosen the sound of this channel.
928      * @see #getSound()
929      */
hasUserSetSound()930     public boolean hasUserSetSound() {
931         return (mUserLockedFields & USER_LOCKED_SOUND) != 0;
932     }
933 
934     /**
935      * @hide
936      */
populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled, Context context)937     public void populateFromXmlForRestore(XmlPullParser parser, boolean pkgInstalled,
938             Context context) {
939         populateFromXml(XmlUtils.makeTyped(parser), true, pkgInstalled, context);
940     }
941 
942     /**
943      * @hide
944      */
945     @SystemApi
populateFromXml(XmlPullParser parser)946     public void populateFromXml(XmlPullParser parser) {
947         populateFromXml(XmlUtils.makeTyped(parser), false, true, null);
948     }
949 
950     /**
951      * If {@param forRestore} is true, {@param Context} MUST be non-null.
952      */
populateFromXml(TypedXmlPullParser parser, boolean forRestore, boolean pkgInstalled, @Nullable Context context)953     private void populateFromXml(TypedXmlPullParser parser, boolean forRestore,
954             boolean pkgInstalled, @Nullable Context context) {
955         Preconditions.checkArgument(!forRestore || context != null,
956                 "forRestore is true but got null context");
957 
958         // Name, id, and importance are set in the constructor.
959         setDescription(parser.getAttributeValue(null, ATT_DESC));
960         setBypassDnd(Notification.PRIORITY_DEFAULT
961                 != safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
962         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
963 
964         Uri sound = safeUri(parser, ATT_SOUND);
965 
966         final AudioAttributes audioAttributes = safeAudioAttributes(parser);
967         final int usage = audioAttributes.getUsage();
968         setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled, usage) : sound,
969                 audioAttributes);
970 
971         enableLights(safeBool(parser, ATT_LIGHTS, false));
972         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
973         setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
974         enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
975         setShowBadge(safeBool(parser, ATT_SHOW_BADGE, false));
976         setDeleted(safeBool(parser, ATT_DELETED, false));
977         setDeletedTimeMs(XmlUtils.readLongAttribute(
978                 parser, ATT_DELETED_TIME_MS, DEFAULT_DELETION_TIME_MS));
979         setGroup(parser.getAttributeValue(null, ATT_GROUP));
980         lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
981         setUserVisibleTaskShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
982         setBlockable(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
983         setAllowBubbles(safeInt(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
984         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
985         setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
986                 parser.getAttributeValue(null, ATT_CONVERSATION_ID));
987         setDemoted(safeBool(parser, ATT_DEMOTE, false));
988         setImportantConversation(safeBool(parser, ATT_IMP_CONVERSATION, false));
989     }
990 
991     /**
992      * Returns whether the sound for this channel was successfully restored
993      *  from backup.
994      * @return false if the sound was not restored successfully. true otherwise (default value)
995      * @hide
996      */
isSoundRestored()997     public boolean isSoundRestored() {
998         return mSoundRestored;
999     }
1000 
1001     @Nullable
getCanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri)1002     private Uri getCanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
1003         if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)) {
1004             return uri;
1005         }
1006 
1007         if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())) {
1008             try {
1009                 contentResolver.getResourceId(uri);
1010                 return uri;
1011             } catch (FileNotFoundException e) {
1012                 return null;
1013             }
1014         }
1015 
1016         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
1017             return uri;
1018         }
1019         return contentResolver.canonicalize(uri);
1020     }
1021 
1022     @Nullable
getUncanonicalizedSoundUri( ContentResolver contentResolver, @NonNull Uri uri, int usage)1023     private Uri getUncanonicalizedSoundUri(
1024             ContentResolver contentResolver, @NonNull Uri uri, int usage) {
1025         if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
1026                 || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
1027                 || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
1028             return uri;
1029         }
1030         int ringtoneType = 0;
1031 
1032         // Consistent with UI(SoundPreferenceController.handlePreferenceTreeClick).
1033         if (AudioAttributes.USAGE_ALARM == usage) {
1034             ringtoneType = RingtoneManager.TYPE_ALARM;
1035         } else if (AudioAttributes.USAGE_NOTIFICATION_RINGTONE == usage) {
1036             ringtoneType = RingtoneManager.TYPE_RINGTONE;
1037         } else {
1038             ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
1039         }
1040         try {
1041             return RingtoneManager.getRingtoneUriForRestore(
1042                     contentResolver, uri.toString(), ringtoneType);
1043         } catch (Exception e) {
1044             Log.e(TAG, "Failed to uncanonicalized sound uri for " + uri + " " + e);
1045             return Settings.System.DEFAULT_NOTIFICATION_URI;
1046         }
1047     }
1048 
1049     /**
1050      * Restore/validate sound Uri from backup
1051      * @param context The Context
1052      * @param uri The sound Uri to restore
1053      * @param pkgInstalled If the parent package is installed
1054      * @return restored and validated Uri
1055      * @hide
1056      */
1057     @Nullable
restoreSoundUri( Context context, @Nullable Uri uri, boolean pkgInstalled, int usage)1058     public Uri restoreSoundUri(
1059             Context context, @Nullable Uri uri, boolean pkgInstalled, int usage) {
1060         if (uri == null || Uri.EMPTY.equals(uri)) {
1061             return null;
1062         }
1063         ContentResolver contentResolver = context.getContentResolver();
1064         // There are backups out there with uncanonical uris (because we fixed this after
1065         // shipping). If uncanonical uris are given to MediaProvider.uncanonicalize it won't
1066         // verify the uri against device storage and we'll possibly end up with a broken uri.
1067         // We then canonicalize the uri to uncanonicalize it back, which means we properly check
1068         // the uri and in the case of not having the resource we end up with the default - better
1069         // than broken. As a side effect we'll canonicalize already canonicalized uris, this is fine
1070         // according to the docs because canonicalize method has to handle canonical uris as well.
1071         Uri canonicalizedUri = getCanonicalizedSoundUri(contentResolver, uri);
1072         if (canonicalizedUri == null) {
1073             // Uri failed to restore with package installed
1074             if (!mSoundRestored && pkgInstalled) {
1075                 mSoundRestored = true;
1076                 // We got a null because the uri in the backup does not exist here, so we return
1077                 // default
1078                 return Settings.System.DEFAULT_NOTIFICATION_URI;
1079             } else {
1080                 // Flag as unrestored and try again later (on package install)
1081                 mSoundRestored = false;
1082                 return uri;
1083             }
1084         }
1085         mSoundRestored = true;
1086         return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri, usage);
1087     }
1088 
1089     /**
1090      * @hide
1091      */
1092     @SystemApi
writeXml(XmlSerializer out)1093     public void writeXml(XmlSerializer out) throws IOException {
1094         writeXml(XmlUtils.makeTyped(out), false, null);
1095     }
1096 
1097     /**
1098      * @hide
1099      */
writeXmlForBackup(XmlSerializer out, Context context)1100     public void writeXmlForBackup(XmlSerializer out, Context context) throws IOException {
1101         writeXml(XmlUtils.makeTyped(out), true, context);
1102     }
1103 
getSoundForBackup(Context context)1104     private Uri getSoundForBackup(Context context) {
1105         Uri sound = getSound();
1106         if (sound == null || Uri.EMPTY.equals(sound)) {
1107             return null;
1108         }
1109         Uri canonicalSound = getCanonicalizedSoundUri(context.getContentResolver(), sound);
1110         if (canonicalSound == null) {
1111             // The content provider does not support canonical uris so we backup the default
1112             return Settings.System.DEFAULT_NOTIFICATION_URI;
1113         }
1114         return canonicalSound;
1115     }
1116 
1117     /**
1118      * If {@param forBackup} is true, {@param Context} MUST be non-null.
1119      */
writeXml(TypedXmlSerializer out, boolean forBackup, @Nullable Context context)1120     private void writeXml(TypedXmlSerializer out, boolean forBackup, @Nullable Context context)
1121             throws IOException {
1122         Preconditions.checkArgument(!forBackup || context != null,
1123                 "forBackup is true but got null context");
1124         out.startTag(null, TAG_CHANNEL);
1125         out.attribute(null, ATT_ID, getId());
1126         if (getName() != null) {
1127             out.attribute(null, ATT_NAME, getName().toString());
1128         }
1129         if (getDescription() != null) {
1130             out.attribute(null, ATT_DESC, getDescription());
1131         }
1132         if (getImportance() != DEFAULT_IMPORTANCE) {
1133             out.attributeInt(null, ATT_IMPORTANCE, getImportance());
1134         }
1135         if (canBypassDnd()) {
1136             out.attributeInt(null, ATT_PRIORITY, Notification.PRIORITY_MAX);
1137         }
1138         if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
1139             out.attributeInt(null, ATT_VISIBILITY, getLockscreenVisibility());
1140         }
1141         Uri sound = forBackup ? getSoundForBackup(context) : getSound();
1142         if (sound != null) {
1143             out.attribute(null, ATT_SOUND, sound.toString());
1144         }
1145         if (getAudioAttributes() != null) {
1146             out.attributeInt(null, ATT_USAGE, getAudioAttributes().getUsage());
1147             out.attributeInt(null, ATT_CONTENT_TYPE, getAudioAttributes().getContentType());
1148             out.attributeInt(null, ATT_FLAGS, getAudioAttributes().getFlags());
1149         }
1150         if (shouldShowLights()) {
1151             out.attributeBoolean(null, ATT_LIGHTS, shouldShowLights());
1152         }
1153         if (getLightColor() != DEFAULT_LIGHT_COLOR) {
1154             out.attributeInt(null, ATT_LIGHT_COLOR, getLightColor());
1155         }
1156         if (shouldVibrate()) {
1157             out.attributeBoolean(null, ATT_VIBRATION_ENABLED, shouldVibrate());
1158         }
1159         if (getVibrationPattern() != null) {
1160             out.attribute(null, ATT_VIBRATION, longArrayToString(getVibrationPattern()));
1161         }
1162         if (getUserLockedFields() != 0) {
1163             out.attributeInt(null, ATT_USER_LOCKED, getUserLockedFields());
1164         }
1165         if (isUserVisibleTaskShown()) {
1166             out.attributeBoolean(null, ATT_FG_SERVICE_SHOWN, isUserVisibleTaskShown());
1167         }
1168         if (canShowBadge()) {
1169             out.attributeBoolean(null, ATT_SHOW_BADGE, canShowBadge());
1170         }
1171         if (isDeleted()) {
1172             out.attributeBoolean(null, ATT_DELETED, isDeleted());
1173         }
1174         if (getDeletedTimeMs() >= 0) {
1175             out.attributeLong(null, ATT_DELETED_TIME_MS, getDeletedTimeMs());
1176         }
1177         if (getGroup() != null) {
1178             out.attribute(null, ATT_GROUP, getGroup());
1179         }
1180         if (isBlockable()) {
1181             out.attributeBoolean(null, ATT_BLOCKABLE_SYSTEM, isBlockable());
1182         }
1183         if (getAllowBubbles() != DEFAULT_ALLOW_BUBBLE) {
1184             out.attributeInt(null, ATT_ALLOW_BUBBLE, getAllowBubbles());
1185         }
1186         if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
1187             out.attributeInt(null, ATT_ORIG_IMP, getOriginalImportance());
1188         }
1189         if (getParentChannelId() != null) {
1190             out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId());
1191         }
1192         if (getConversationId() != null) {
1193             out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
1194         }
1195         if (isDemoted()) {
1196             out.attributeBoolean(null, ATT_DEMOTE, isDemoted());
1197         }
1198         if (isImportantConversation()) {
1199             out.attributeBoolean(null, ATT_IMP_CONVERSATION, isImportantConversation());
1200         }
1201 
1202         // mImportanceLockedDefaultApp has a different source of truth and so isn't written to
1203         // this xml file
1204 
1205         out.endTag(null, TAG_CHANNEL);
1206     }
1207 
1208     /**
1209      * @hide
1210      */
1211     @SystemApi
toJson()1212     public JSONObject toJson() throws JSONException {
1213         JSONObject record = new JSONObject();
1214         record.put(ATT_ID, getId());
1215         record.put(ATT_NAME, getName());
1216         record.put(ATT_DESC, getDescription());
1217         if (getImportance() != DEFAULT_IMPORTANCE) {
1218             record.put(ATT_IMPORTANCE,
1219                     NotificationListenerService.Ranking.importanceToString(getImportance()));
1220         }
1221         if (canBypassDnd()) {
1222             record.put(ATT_PRIORITY, Notification.PRIORITY_MAX);
1223         }
1224         if (getLockscreenVisibility() != DEFAULT_VISIBILITY) {
1225             record.put(ATT_VISIBILITY, Notification.visibilityToString(getLockscreenVisibility()));
1226         }
1227         if (getSound() != null) {
1228             record.put(ATT_SOUND, getSound().toString());
1229         }
1230         if (getAudioAttributes() != null) {
1231             record.put(ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
1232             record.put(ATT_CONTENT_TYPE,
1233                     Integer.toString(getAudioAttributes().getContentType()));
1234             record.put(ATT_FLAGS, Integer.toString(getAudioAttributes().getFlags()));
1235         }
1236         record.put(ATT_LIGHTS, Boolean.toString(shouldShowLights()));
1237         record.put(ATT_LIGHT_COLOR, Integer.toString(getLightColor()));
1238         record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
1239         record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
1240         record.put(ATT_FG_SERVICE_SHOWN, Boolean.toString(isUserVisibleTaskShown()));
1241         record.put(ATT_VIBRATION, longArrayToString(getVibrationPattern()));
1242         record.put(ATT_SHOW_BADGE, Boolean.toString(canShowBadge()));
1243         record.put(ATT_DELETED, Boolean.toString(isDeleted()));
1244         record.put(ATT_DELETED_TIME_MS, Long.toString(getDeletedTimeMs()));
1245         record.put(ATT_GROUP, getGroup());
1246         record.put(ATT_BLOCKABLE_SYSTEM, isBlockable());
1247         record.put(ATT_ALLOW_BUBBLE, getAllowBubbles());
1248         // TODO: original importance
1249         return record;
1250     }
1251 
safeAudioAttributes(TypedXmlPullParser parser)1252     private static AudioAttributes safeAudioAttributes(TypedXmlPullParser parser) {
1253         int usage = safeInt(parser, ATT_USAGE, AudioAttributes.USAGE_NOTIFICATION);
1254         int contentType = safeInt(parser, ATT_CONTENT_TYPE,
1255                 AudioAttributes.CONTENT_TYPE_SONIFICATION);
1256         int flags = safeInt(parser, ATT_FLAGS, 0);
1257         return new AudioAttributes.Builder()
1258                 .setUsage(usage)
1259                 .setContentType(contentType)
1260                 .setFlags(flags)
1261                 .build();
1262     }
1263 
safeUri(TypedXmlPullParser parser, String att)1264     private static Uri safeUri(TypedXmlPullParser parser, String att) {
1265         final String val = parser.getAttributeValue(null, att);
1266         return val == null ? null : Uri.parse(val);
1267     }
1268 
safeInt(TypedXmlPullParser parser, String att, int defValue)1269     private static int safeInt(TypedXmlPullParser parser, String att, int defValue) {
1270         return parser.getAttributeInt(null, att, defValue);
1271     }
1272 
safeBool(TypedXmlPullParser parser, String att, boolean defValue)1273     private static boolean safeBool(TypedXmlPullParser parser, String att, boolean defValue) {
1274         return parser.getAttributeBoolean(null, att, defValue);
1275     }
1276 
safeLongArray(TypedXmlPullParser parser, String att, long[] defValue)1277     private static long[] safeLongArray(TypedXmlPullParser parser, String att, long[] defValue) {
1278         final String attributeValue = parser.getAttributeValue(null, att);
1279         if (TextUtils.isEmpty(attributeValue)) return defValue;
1280         String[] values = attributeValue.split(DELIMITER);
1281         long[] longValues = new long[values.length];
1282         for (int i = 0; i < values.length; i++) {
1283             try {
1284                 longValues[i] = Long.parseLong(values[i]);
1285             } catch (NumberFormatException e) {
1286                 longValues[i] = 0;
1287             }
1288         }
1289         return longValues;
1290     }
1291 
longArrayToString(long[] values)1292     private static String longArrayToString(long[] values) {
1293         StringBuilder sb = new StringBuilder();
1294         if (values != null && values.length > 0) {
1295             for (int i = 0; i < values.length - 1; i++) {
1296                 sb.append(values[i]).append(DELIMITER);
1297             }
1298             sb.append(values[values.length - 1]);
1299         }
1300         return sb.toString();
1301     }
1302 
1303     public static final @android.annotation.NonNull Creator<NotificationChannel> CREATOR =
1304             new Creator<NotificationChannel>() {
1305         @Override
1306         public NotificationChannel createFromParcel(Parcel in) {
1307             return new NotificationChannel(in);
1308         }
1309 
1310         @Override
1311         public NotificationChannel[] newArray(int size) {
1312             return new NotificationChannel[size];
1313         }
1314     };
1315 
1316     @Override
describeContents()1317     public int describeContents() {
1318         return 0;
1319     }
1320 
1321     @Override
equals(@ullable Object o)1322     public boolean equals(@Nullable Object o) {
1323         if (this == o) return true;
1324         if (o == null || getClass() != o.getClass()) return false;
1325         NotificationChannel that = (NotificationChannel) o;
1326         return getImportance() == that.getImportance()
1327                 && mBypassDnd == that.mBypassDnd
1328                 && getLockscreenVisibility() == that.getLockscreenVisibility()
1329                 && mLights == that.mLights
1330                 && getLightColor() == that.getLightColor()
1331                 && getUserLockedFields() == that.getUserLockedFields()
1332                 && isUserVisibleTaskShown() == that.isUserVisibleTaskShown()
1333                 && mVibrationEnabled == that.mVibrationEnabled
1334                 && mShowBadge == that.mShowBadge
1335                 && isDeleted() == that.isDeleted()
1336                 && getDeletedTimeMs() == that.getDeletedTimeMs()
1337                 && isBlockable() == that.isBlockable()
1338                 && mAllowBubbles == that.mAllowBubbles
1339                 && Objects.equals(getId(), that.getId())
1340                 && Objects.equals(getName(), that.getName())
1341                 && Objects.equals(mDesc, that.mDesc)
1342                 && Objects.equals(getSound(), that.getSound())
1343                 && Arrays.equals(mVibration, that.mVibration)
1344                 && Objects.equals(getGroup(), that.getGroup())
1345                 && Objects.equals(getAudioAttributes(), that.getAudioAttributes())
1346                 && mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
1347                 && mOriginalImportance == that.mOriginalImportance
1348                 && Objects.equals(getParentChannelId(), that.getParentChannelId())
1349                 && Objects.equals(getConversationId(), that.getConversationId())
1350                 && isDemoted() == that.isDemoted()
1351                 && isImportantConversation() == that.isImportantConversation();
1352     }
1353 
1354     @Override
hashCode()1355     public int hashCode() {
1356         int result = Objects.hash(getId(), getName(), mDesc, getImportance(), mBypassDnd,
1357                 getLockscreenVisibility(), getSound(), mLights, getLightColor(),
1358                 getUserLockedFields(), isUserVisibleTaskShown(),
1359                 mVibrationEnabled, mShowBadge, isDeleted(), getDeletedTimeMs(),
1360                 getGroup(), getAudioAttributes(), isBlockable(), mAllowBubbles,
1361                 mImportanceLockedDefaultApp, mOriginalImportance,
1362                 mParentId, mConversationId, mDemoted, mImportantConvo);
1363         result = 31 * result + Arrays.hashCode(mVibration);
1364         return result;
1365     }
1366 
1367     /** @hide */
dump(PrintWriter pw, String prefix, boolean redacted)1368     public void dump(PrintWriter pw, String prefix, boolean redacted) {
1369         String redactedName = redacted ? TextUtils.trimToLengthWithEllipsis(mName, 3) : mName;
1370         String output = "NotificationChannel{"
1371                 + "mId='" + mId + '\''
1372                 + ", mName=" + redactedName
1373                 + getFieldsString()
1374                 + '}';
1375         pw.println(prefix + output);
1376     }
1377 
1378     @Override
toString()1379     public String toString() {
1380         return "NotificationChannel{"
1381                 + "mId='" + mId + '\''
1382                 + ", mName=" + mName
1383                 + getFieldsString()
1384                 + '}';
1385     }
1386 
getFieldsString()1387     private String getFieldsString() {
1388         return  ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
1389                 + ", mImportance=" + mImportance
1390                 + ", mBypassDnd=" + mBypassDnd
1391                 + ", mLockscreenVisibility=" + mLockscreenVisibility
1392                 + ", mSound=" + mSound
1393                 + ", mLights=" + mLights
1394                 + ", mLightColor=" + mLightColor
1395                 + ", mVibration=" + Arrays.toString(mVibration)
1396                 + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
1397                 + ", mUserVisibleTaskShown=" + mUserVisibleTaskShown
1398                 + ", mVibrationEnabled=" + mVibrationEnabled
1399                 + ", mShowBadge=" + mShowBadge
1400                 + ", mDeleted=" + mDeleted
1401                 + ", mDeletedTimeMs=" + mDeletedTime
1402                 + ", mGroup='" + mGroup + '\''
1403                 + ", mAudioAttributes=" + mAudioAttributes
1404                 + ", mBlockableSystem=" + mBlockableSystem
1405                 + ", mAllowBubbles=" + mAllowBubbles
1406                 + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
1407                 + ", mOriginalImp=" + mOriginalImportance
1408                 + ", mParent=" + mParentId
1409                 + ", mConversationId=" + mConversationId
1410                 + ", mDemoted=" + mDemoted
1411                 + ", mImportantConvo=" + mImportantConvo;
1412     }
1413 
1414     /** @hide */
dumpDebug(ProtoOutputStream proto, long fieldId)1415     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
1416         final long token = proto.start(fieldId);
1417 
1418         proto.write(NotificationChannelProto.ID, mId);
1419         proto.write(NotificationChannelProto.NAME, mName);
1420         proto.write(NotificationChannelProto.DESCRIPTION, mDesc);
1421         proto.write(NotificationChannelProto.IMPORTANCE, mImportance);
1422         proto.write(NotificationChannelProto.CAN_BYPASS_DND, mBypassDnd);
1423         proto.write(NotificationChannelProto.LOCKSCREEN_VISIBILITY, mLockscreenVisibility);
1424         if (mSound != null) {
1425             proto.write(NotificationChannelProto.SOUND, mSound.toString());
1426         }
1427         proto.write(NotificationChannelProto.USE_LIGHTS, mLights);
1428         proto.write(NotificationChannelProto.LIGHT_COLOR, mLightColor);
1429         if (mVibration != null) {
1430             for (long v : mVibration) {
1431                 proto.write(NotificationChannelProto.VIBRATION, v);
1432             }
1433         }
1434         proto.write(NotificationChannelProto.USER_LOCKED_FIELDS, mUserLockedFields);
1435         proto.write(NotificationChannelProto.USER_VISIBLE_TASK_SHOWN, mUserVisibleTaskShown);
1436         proto.write(NotificationChannelProto.IS_VIBRATION_ENABLED, mVibrationEnabled);
1437         proto.write(NotificationChannelProto.SHOW_BADGE, mShowBadge);
1438         proto.write(NotificationChannelProto.IS_DELETED, mDeleted);
1439         proto.write(NotificationChannelProto.GROUP, mGroup);
1440         if (mAudioAttributes != null) {
1441             mAudioAttributes.dumpDebug(proto, NotificationChannelProto.AUDIO_ATTRIBUTES);
1442         }
1443         proto.write(NotificationChannelProto.IS_BLOCKABLE_SYSTEM, mBlockableSystem);
1444         proto.write(NotificationChannelProto.ALLOW_APP_OVERLAY, mAllowBubbles);
1445 
1446         proto.end(token);
1447     }
1448 }
1449