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