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