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