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