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