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