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