Home | History | Annotate | Download | only in app
      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      */
    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      */
    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
    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      */
    296     public void lockFields(int field) {
    297         mUserLockedFields |= field;
    298     }
    299 
    300     /**
    301      * @hide
    302      */
    303     public void unlockFields(int field) {
    304         mUserLockedFields &= ~field;
    305     }
    306 
    307     /**
    308      * @hide
    309      */
    310     public void setFgServiceShown(boolean shown) {
    311         mFgServiceShown = shown;
    312     }
    313 
    314     /**
    315      * @hide
    316      */
    317     public void setDeleted(boolean deleted) {
    318         mDeleted = deleted;
    319     }
    320 
    321     /**
    322      * @hide
    323      */
    324     @UnsupportedAppUsage
    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      */
    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      */
    346     public void setDescription(String description) {
    347         mDesc = getTrimmedString(description);
    348     }
    349 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    494     public void setAllowBubbles(boolean allowBubbles) {
    495         mAllowBubbles = allowBubbles;
    496     }
    497 
    498     /**
    499      * Returns the id of this channel.
    500      */
    501     public String getId() {
    502         return mId;
    503     }
    504 
    505     /**
    506      * Returns the user visible name of this channel.
    507      */
    508     public CharSequence getName() {
    509         return mName;
    510     }
    511 
    512     /**
    513      * Returns the user visible description of this channel.
    514      */
    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      */
    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      */
    535     public boolean canBypassDnd() {
    536         return mBypassDnd;
    537     }
    538 
    539     /**
    540      * Returns the notification sound for this channel.
    541      */
    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      */
    549     public AudioAttributes getAudioAttributes() {
    550         return mAudioAttributes;
    551     }
    552 
    553     /**
    554      * Returns whether notifications posted to this channel trigger notification lights.
    555      */
    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      */
    564     public int getLightColor() {
    565         return mLightColor;
    566     }
    567 
    568     /**
    569      * Returns whether notifications posted to this channel always vibrate.
    570      */
    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      */
    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      */
    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      */
    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      */
    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      */
    614     public boolean canBubble() {
    615         return mAllowBubbles;
    616     }
    617 
    618     /**
    619      * @hide
    620      */
    621     @SystemApi
    622     public boolean isDeleted() {
    623         return mDeleted;
    624     }
    625 
    626     /**
    627      * @hide
    628      */
    629     @SystemApi
    630     public int getUserLockedFields() {
    631         return mUserLockedFields;
    632     }
    633 
    634     /**
    635      * @hide
    636      */
    637     public boolean isFgServiceShown() {
    638         return mFgServiceShown;
    639     }
    640 
    641     /**
    642      * @hide
    643      */
    644     public boolean isBlockableSystem() {
    645         return mBlockableSystem;
    646     }
    647 
    648     /**
    649      * @hide
    650      */
    651     @TestApi
    652     public void setImportanceLockedByOEM(boolean locked) {
    653         mImportanceLockedByOEM = locked;
    654     }
    655 
    656     /**
    657      * @hide
    658      */
    659     @TestApi
    660     public void setImportanceLockedByCriticalDeviceFunction(boolean locked) {
    661         mImportanceLockedDefaultApp = locked;
    662     }
    663 
    664     /**
    665      * @hide
    666      */
    667     @TestApi
    668     public boolean isImportanceLockedByOEM() {
    669         return mImportanceLockedByOEM;
    670     }
    671 
    672     /**
    673      * @hide
    674      */
    675     @TestApi
    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      */
    685     public boolean hasUserSetImportance() {
    686         return (mUserLockedFields & USER_LOCKED_IMPORTANCE) != 0;
    687     }
    688 
    689     /**
    690      * @hide
    691      */
    692     public void populateFromXmlForRestore(XmlPullParser parser, Context context) {
    693         populateFromXml(parser, true, context);
    694     }
    695 
    696     /**
    697      * @hide
    698      */
    699     @SystemApi
    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      */
    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
    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
    759     public void writeXml(XmlSerializer out) throws IOException {
    760         writeXml(out, false, null);
    761     }
    762 
    763     /**
    764      * @hide
    765      */
    766     public void writeXmlForBackup(XmlSerializer out, Context context) throws IOException {
    767         writeXml(out, true, context);
    768     }
    769 
    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      */
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    979     public int describeContents() {
    980         return 0;
    981     }
    982 
    983     @Override
    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
   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 */
   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
   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 */
   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