Notification.java revision 0c299d4d6316d14e43d386b4562782083fe3c886
1/* 2 * Copyright (C) 2007 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 17package android.app; 18 19import android.annotation.ColorInt; 20import android.annotation.DrawableRes; 21import android.annotation.IntDef; 22import android.annotation.NonNull; 23import android.annotation.SdkConstant; 24import android.annotation.SdkConstant.SdkConstantType; 25import android.annotation.SystemApi; 26import android.content.Context; 27import android.content.Intent; 28import android.content.pm.ApplicationInfo; 29import android.content.pm.PackageManager; 30import android.content.pm.PackageManager.NameNotFoundException; 31import android.content.res.ColorStateList; 32import android.graphics.Bitmap; 33import android.graphics.Canvas; 34import android.graphics.Color; 35import android.graphics.PorterDuff; 36import android.graphics.drawable.Drawable; 37import android.graphics.drawable.Icon; 38import android.media.AudioAttributes; 39import android.media.AudioManager; 40import android.media.session.MediaSession; 41import android.net.Uri; 42import android.os.BadParcelableException; 43import android.os.Build; 44import android.os.Bundle; 45import android.os.Parcel; 46import android.os.Parcelable; 47import android.os.SystemClock; 48import android.os.UserHandle; 49import android.text.BidiFormatter; 50import android.text.SpannableStringBuilder; 51import android.text.Spanned; 52import android.text.TextUtils; 53import android.text.style.AbsoluteSizeSpan; 54import android.text.style.BackgroundColorSpan; 55import android.text.style.CharacterStyle; 56import android.text.style.ForegroundColorSpan; 57import android.text.style.RelativeSizeSpan; 58import android.text.style.TextAppearanceSpan; 59import android.util.ArraySet; 60import android.util.Log; 61import android.util.SparseArray; 62import android.view.Gravity; 63import android.view.NotificationHeaderView; 64import android.view.View; 65import android.view.ViewGroup; 66import android.widget.ProgressBar; 67import android.widget.RemoteViews; 68 69import com.android.internal.R; 70import com.android.internal.util.ArrayUtils; 71import com.android.internal.util.NotificationColorUtil; 72 73import java.lang.annotation.Retention; 74import java.lang.annotation.RetentionPolicy; 75import java.lang.reflect.Constructor; 76import java.util.ArrayList; 77import java.util.Arrays; 78import java.util.Collections; 79import java.util.List; 80import java.util.Set; 81 82/** 83 * A class that represents how a persistent notification is to be presented to 84 * the user using the {@link android.app.NotificationManager}. 85 * 86 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 87 * easier to construct Notifications.</p> 88 * 89 * <div class="special reference"> 90 * <h3>Developer Guides</h3> 91 * <p>For a guide to creating notifications, read the 92 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 93 * developer guide.</p> 94 * </div> 95 */ 96public class Notification implements Parcelable 97{ 98 private static final String TAG = "Notification"; 99 100 /** 101 * An activity that provides a user interface for adjusting notification preferences for its 102 * containing application. Optional but recommended for apps that post 103 * {@link android.app.Notification Notifications}. 104 */ 105 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 106 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES 107 = "android.intent.category.NOTIFICATION_PREFERENCES"; 108 109 /** 110 * Use all default values (where applicable). 111 */ 112 public static final int DEFAULT_ALL = ~0; 113 114 /** 115 * Use the default notification sound. This will ignore any given 116 * {@link #sound}. 117 * 118 * <p> 119 * A notification that is noisy is more likely to be presented as a heads-up notification. 120 * </p> 121 * 122 * @see #defaults 123 */ 124 125 public static final int DEFAULT_SOUND = 1; 126 127 /** 128 * Use the default notification vibrate. This will ignore any given 129 * {@link #vibrate}. Using phone vibration requires the 130 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 131 * 132 * <p> 133 * A notification that vibrates is more likely to be presented as a heads-up notification. 134 * </p> 135 * 136 * @see #defaults 137 */ 138 139 public static final int DEFAULT_VIBRATE = 2; 140 141 /** 142 * Use the default notification lights. This will ignore the 143 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 144 * {@link #ledOnMS}. 145 * 146 * @see #defaults 147 */ 148 149 public static final int DEFAULT_LIGHTS = 4; 150 151 /** 152 * Maximum length of CharSequences accepted by Builder and friends. 153 * 154 * <p> 155 * Avoids spamming the system with overly large strings such as full e-mails. 156 */ 157 private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024; 158 159 /** 160 * Maximum entries of reply text that are accepted by Builder and friends. 161 */ 162 private static final int MAX_REPLY_HISTORY = 5; 163 164 /** 165 * A timestamp related to this notification, in milliseconds since the epoch. 166 * 167 * Default value: {@link System#currentTimeMillis() Now}. 168 * 169 * Choose a timestamp that will be most relevant to the user. For most finite events, this 170 * corresponds to the time the event happened (or will happen, in the case of events that have 171 * yet to occur but about which the user is being informed). Indefinite events should be 172 * timestamped according to when the activity began. 173 * 174 * Some examples: 175 * 176 * <ul> 177 * <li>Notification of a new chat message should be stamped when the message was received.</li> 178 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 179 * <li>Notification of a completed file download should be stamped when the download finished.</li> 180 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 181 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 182 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 183 * </ul> 184 * 185 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown 186 * anymore by default and must be opted into by using 187 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 188 */ 189 public long when; 190 191 /** 192 * The creation time of the notification 193 */ 194 private long creationTime; 195 196 /** 197 * The resource id of a drawable to use as the icon in the status bar. 198 * 199 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead. 200 */ 201 @Deprecated 202 @DrawableRes 203 public int icon; 204 205 /** 206 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 207 * leave it at its default value of 0. 208 * 209 * @see android.widget.ImageView#setImageLevel 210 * @see android.graphics.drawable.Drawable#setLevel 211 */ 212 public int iconLevel; 213 214 /** 215 * The number of events that this notification represents. For example, in a new mail 216 * notification, this could be the number of unread messages. 217 * 218 * The system may or may not use this field to modify the appearance of the notification. For 219 * example, before {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this number was 220 * superimposed over the icon in the status bar. Starting with 221 * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, the template used by 222 * {@link Notification.Builder} has displayed the number in the expanded notification view. 223 * 224 * If the number is 0 or negative, it is never shown. 225 * 226 * @deprecated this number is not shown anymore 227 */ 228 @Deprecated 229 public int number; 230 231 /** 232 * The intent to execute when the expanded status entry is clicked. If 233 * this is an activity, it must include the 234 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 235 * that you take care of task management as described in the 236 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 237 * Stack</a> document. In particular, make sure to read the notification section 238 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling 239 * Notifications</a> for the correct ways to launch an application from a 240 * notification. 241 */ 242 public PendingIntent contentIntent; 243 244 /** 245 * The intent to execute when the notification is explicitly dismissed by the user, either with 246 * the "Clear All" button or by swiping it away individually. 247 * 248 * This probably shouldn't be launching an activity since several of those will be sent 249 * at the same time. 250 */ 251 public PendingIntent deleteIntent; 252 253 /** 254 * An intent to launch instead of posting the notification to the status bar. 255 * 256 * <p> 257 * The system UI may choose to display a heads-up notification, instead of 258 * launching this intent, while the user is using the device. 259 * </p> 260 * 261 * @see Notification.Builder#setFullScreenIntent 262 */ 263 public PendingIntent fullScreenIntent; 264 265 /** 266 * Text that summarizes this notification for accessibility services. 267 * 268 * As of the L release, this text is no longer shown on screen, but it is still useful to 269 * accessibility services (where it serves as an audible announcement of the notification's 270 * appearance). 271 * 272 * @see #tickerView 273 */ 274 public CharSequence tickerText; 275 276 /** 277 * Formerly, a view showing the {@link #tickerText}. 278 * 279 * No longer displayed in the status bar as of API 21. 280 */ 281 @Deprecated 282 public RemoteViews tickerView; 283 284 /** 285 * The view that will represent this notification in the notification list (which is pulled 286 * down from the status bar). 287 * 288 * As of N, this field may be null. The notification view is determined by the inputs 289 * to {@link Notification.Builder}; a custom RemoteViews can optionally be 290 * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. 291 */ 292 @Deprecated 293 public RemoteViews contentView; 294 295 /** 296 * A large-format version of {@link #contentView}, giving the Notification an 297 * opportunity to show more detail. The system UI may choose to show this 298 * instead of the normal content view at its discretion. 299 * 300 * As of N, this field may be null. The expanded notification view is determined by the 301 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 302 * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. 303 */ 304 @Deprecated 305 public RemoteViews bigContentView; 306 307 308 /** 309 * A medium-format version of {@link #contentView}, providing the Notification an 310 * opportunity to add action buttons to contentView. At its discretion, the system UI may 311 * choose to show this as a heads-up notification, which will pop up so the user can see 312 * it without leaving their current activity. 313 * 314 * As of N, this field may be null. The heads-up notification view is determined by the 315 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 316 * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. 317 */ 318 @Deprecated 319 public RemoteViews headsUpContentView; 320 321 /** 322 * A large bitmap to be shown in the notification content area. 323 * 324 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead. 325 */ 326 @Deprecated 327 public Bitmap largeIcon; 328 329 /** 330 * The sound to play. 331 * 332 * <p> 333 * A notification that is noisy is more likely to be presented as a heads-up notification. 334 * </p> 335 * 336 * <p> 337 * To play the default notification sound, see {@link #defaults}. 338 * </p> 339 */ 340 public Uri sound; 341 342 /** 343 * Use this constant as the value for audioStreamType to request that 344 * the default stream type for notifications be used. Currently the 345 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 346 * 347 * @deprecated Use {@link #audioAttributes} instead. 348 */ 349 @Deprecated 350 public static final int STREAM_DEFAULT = -1; 351 352 /** 353 * The audio stream type to use when playing the sound. 354 * Should be one of the STREAM_ constants from 355 * {@link android.media.AudioManager}. 356 * 357 * @deprecated Use {@link #audioAttributes} instead. 358 */ 359 @Deprecated 360 public int audioStreamType = STREAM_DEFAULT; 361 362 /** 363 * The default value of {@link #audioAttributes}. 364 */ 365 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() 366 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 367 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 368 .build(); 369 370 /** 371 * The {@link AudioAttributes audio attributes} to use when playing the sound. 372 */ 373 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 374 375 /** 376 * The pattern with which to vibrate. 377 * 378 * <p> 379 * To vibrate the default pattern, see {@link #defaults}. 380 * </p> 381 * 382 * <p> 383 * A notification that vibrates is more likely to be presented as a heads-up notification. 384 * </p> 385 * 386 * @see android.os.Vibrator#vibrate(long[],int) 387 */ 388 public long[] vibrate; 389 390 /** 391 * The color of the led. The hardware will do its best approximation. 392 * 393 * @see #FLAG_SHOW_LIGHTS 394 * @see #flags 395 */ 396 @ColorInt 397 public int ledARGB; 398 399 /** 400 * The number of milliseconds for the LED to be on while it's flashing. 401 * The hardware will do its best approximation. 402 * 403 * @see #FLAG_SHOW_LIGHTS 404 * @see #flags 405 */ 406 public int ledOnMS; 407 408 /** 409 * The number of milliseconds for the LED to be off while it's flashing. 410 * The hardware will do its best approximation. 411 * 412 * @see #FLAG_SHOW_LIGHTS 413 * @see #flags 414 */ 415 public int ledOffMS; 416 417 /** 418 * Specifies which values should be taken from the defaults. 419 * <p> 420 * To set, OR the desired from {@link #DEFAULT_SOUND}, 421 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 422 * values, use {@link #DEFAULT_ALL}. 423 * </p> 424 */ 425 public int defaults; 426 427 /** 428 * Bit to be bitwise-ored into the {@link #flags} field that should be 429 * set if you want the LED on for this notification. 430 * <ul> 431 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 432 * or 0 for both ledOnMS and ledOffMS.</li> 433 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 434 * <li>To flash the LED, pass the number of milliseconds that it should 435 * be on and off to ledOnMS and ledOffMS.</li> 436 * </ul> 437 * <p> 438 * Since hardware varies, you are not guaranteed that any of the values 439 * you pass are honored exactly. Use the system defaults (TODO) if possible 440 * because they will be set to values that work on any given hardware. 441 * <p> 442 * The alpha channel must be set for forward compatibility. 443 * 444 */ 445 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 446 447 /** 448 * Bit to be bitwise-ored into the {@link #flags} field that should be 449 * set if this notification is in reference to something that is ongoing, 450 * like a phone call. It should not be set if this notification is in 451 * reference to something that happened at a particular point in time, 452 * like a missed phone call. 453 */ 454 public static final int FLAG_ONGOING_EVENT = 0x00000002; 455 456 /** 457 * Bit to be bitwise-ored into the {@link #flags} field that if set, 458 * the audio will be repeated until the notification is 459 * cancelled or the notification window is opened. 460 */ 461 public static final int FLAG_INSISTENT = 0x00000004; 462 463 /** 464 * Bit to be bitwise-ored into the {@link #flags} field that should be 465 * set if you would only like the sound, vibrate and ticker to be played 466 * if the notification was not already showing. 467 */ 468 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 469 470 /** 471 * Bit to be bitwise-ored into the {@link #flags} field that should be 472 * set if the notification should be canceled when it is clicked by the 473 * user. 474 */ 475 public static final int FLAG_AUTO_CANCEL = 0x00000010; 476 477 /** 478 * Bit to be bitwise-ored into the {@link #flags} field that should be 479 * set if the notification should not be canceled when the user clicks 480 * the Clear all button. 481 */ 482 public static final int FLAG_NO_CLEAR = 0x00000020; 483 484 /** 485 * Bit to be bitwise-ored into the {@link #flags} field that should be 486 * set if this notification represents a currently running service. This 487 * will normally be set for you by {@link Service#startForeground}. 488 */ 489 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 490 491 /** 492 * Obsolete flag indicating high-priority notifications; use the priority field instead. 493 * 494 * @deprecated Use {@link #priority} with a positive value. 495 */ 496 @Deprecated 497 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 498 499 /** 500 * Bit to be bitswise-ored into the {@link #flags} field that should be 501 * set if this notification is relevant to the current device only 502 * and it is not recommended that it bridge to other devices. 503 */ 504 public static final int FLAG_LOCAL_ONLY = 0x00000100; 505 506 /** 507 * Bit to be bitswise-ored into the {@link #flags} field that should be 508 * set if this notification is the group summary for a group of notifications. 509 * Grouped notifications may display in a cluster or stack on devices which 510 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 511 */ 512 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 513 514 /** 515 * Bit to be bitswise-ored into the {@link #flags} field that should be 516 * set if this notification is the group summary for an auto-group of notifications. 517 * 518 * @hide 519 */ 520 @SystemApi 521 public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400; 522 523 public int flags; 524 525 /** @hide */ 526 @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX}) 527 @Retention(RetentionPolicy.SOURCE) 528 public @interface Priority {} 529 530 /** 531 * Default notification {@link #priority}. If your application does not prioritize its own 532 * notifications, use this value for all notifications. 533 */ 534 public static final int PRIORITY_DEFAULT = 0; 535 536 /** 537 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 538 * items smaller, or at a different position in the list, compared with your app's 539 * {@link #PRIORITY_DEFAULT} items. 540 */ 541 public static final int PRIORITY_LOW = -1; 542 543 /** 544 * Lowest {@link #priority}; these items might not be shown to the user except under special 545 * circumstances, such as detailed notification logs. 546 */ 547 public static final int PRIORITY_MIN = -2; 548 549 /** 550 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 551 * show these items larger, or at a different position in notification lists, compared with 552 * your app's {@link #PRIORITY_DEFAULT} items. 553 */ 554 public static final int PRIORITY_HIGH = 1; 555 556 /** 557 * Highest {@link #priority}, for your application's most important items that require the 558 * user's prompt attention or input. 559 */ 560 public static final int PRIORITY_MAX = 2; 561 562 /** 563 * Relative priority for this notification. 564 * 565 * Priority is an indication of how much of the user's valuable attention should be consumed by 566 * this notification. Low-priority notifications may be hidden from the user in certain 567 * situations, while the user might be interrupted for a higher-priority notification. The 568 * system will make a determination about how to interpret this priority when presenting 569 * the notification. 570 * 571 * <p> 572 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 573 * as a heads-up notification. 574 * </p> 575 * 576 */ 577 @Priority 578 public int priority; 579 580 /** 581 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 582 * to be applied by the standard Style templates when presenting this notification. 583 * 584 * The current template design constructs a colorful header image by overlaying the 585 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 586 * ignored. 587 */ 588 @ColorInt 589 public int color = COLOR_DEFAULT; 590 591 /** 592 * Special value of {@link #color} telling the system not to decorate this notification with 593 * any special color but instead use default colors when presenting this notification. 594 */ 595 @ColorInt 596 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 597 598 /** 599 * Special value of {@link #color} used as a place holder for an invalid color. 600 */ 601 @ColorInt 602 private static final int COLOR_INVALID = 1; 603 604 /** 605 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 606 * the notification's presence and contents in untrusted situations (namely, on the secure 607 * lockscreen). 608 * 609 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 610 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 611 * shown in all situations, but the contents are only available if the device is unlocked for 612 * the appropriate user. 613 * 614 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 615 * can be read even in an "insecure" context (that is, above a secure lockscreen). 616 * To modify the public version of this notification—for example, to redact some portions—see 617 * {@link Builder#setPublicVersion(Notification)}. 618 * 619 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 620 * and ticker until the user has bypassed the lockscreen. 621 */ 622 public int visibility; 623 624 /** 625 * Notification visibility: Show this notification in its entirety on all lockscreens. 626 * 627 * {@see #visibility} 628 */ 629 public static final int VISIBILITY_PUBLIC = 1; 630 631 /** 632 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 633 * private information on secure lockscreens. 634 * 635 * {@see #visibility} 636 */ 637 public static final int VISIBILITY_PRIVATE = 0; 638 639 /** 640 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 641 * 642 * {@see #visibility} 643 */ 644 public static final int VISIBILITY_SECRET = -1; 645 646 /** 647 * Notification category: incoming call (voice or video) or similar synchronous communication request. 648 */ 649 public static final String CATEGORY_CALL = "call"; 650 651 /** 652 * Notification category: incoming direct message (SMS, instant message, etc.). 653 */ 654 public static final String CATEGORY_MESSAGE = "msg"; 655 656 /** 657 * Notification category: asynchronous bulk message (email). 658 */ 659 public static final String CATEGORY_EMAIL = "email"; 660 661 /** 662 * Notification category: calendar event. 663 */ 664 public static final String CATEGORY_EVENT = "event"; 665 666 /** 667 * Notification category: promotion or advertisement. 668 */ 669 public static final String CATEGORY_PROMO = "promo"; 670 671 /** 672 * Notification category: alarm or timer. 673 */ 674 public static final String CATEGORY_ALARM = "alarm"; 675 676 /** 677 * Notification category: progress of a long-running background operation. 678 */ 679 public static final String CATEGORY_PROGRESS = "progress"; 680 681 /** 682 * Notification category: social network or sharing update. 683 */ 684 public static final String CATEGORY_SOCIAL = "social"; 685 686 /** 687 * Notification category: error in background operation or authentication status. 688 */ 689 public static final String CATEGORY_ERROR = "err"; 690 691 /** 692 * Notification category: media transport control for playback. 693 */ 694 public static final String CATEGORY_TRANSPORT = "transport"; 695 696 /** 697 * Notification category: system or device status update. Reserved for system use. 698 */ 699 public static final String CATEGORY_SYSTEM = "sys"; 700 701 /** 702 * Notification category: indication of running background service. 703 */ 704 public static final String CATEGORY_SERVICE = "service"; 705 706 /** 707 * Notification category: a specific, timely recommendation for a single thing. 708 * For example, a news app might want to recommend a news story it believes the user will 709 * want to read next. 710 */ 711 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 712 713 /** 714 * Notification category: ongoing information about device or contextual status. 715 */ 716 public static final String CATEGORY_STATUS = "status"; 717 718 /** 719 * Notification category: user-scheduled reminder. 720 */ 721 public static final String CATEGORY_REMINDER = "reminder"; 722 723 /** 724 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 725 * that best describes this Notification. May be used by the system for ranking and filtering. 726 */ 727 public String category; 728 729 private String mGroupKey; 730 731 /** 732 * Get the key used to group this notification into a cluster or stack 733 * with other notifications on devices which support such rendering. 734 */ 735 public String getGroup() { 736 return mGroupKey; 737 } 738 739 private String mSortKey; 740 741 /** 742 * Get a sort key that orders this notification among other notifications from the 743 * same package. This can be useful if an external sort was already applied and an app 744 * would like to preserve this. Notifications will be sorted lexicographically using this 745 * value, although providing different priorities in addition to providing sort key may 746 * cause this value to be ignored. 747 * 748 * <p>This sort key can also be used to order members of a notification group. See 749 * {@link Builder#setGroup}. 750 * 751 * @see String#compareTo(String) 752 */ 753 public String getSortKey() { 754 return mSortKey; 755 } 756 757 /** 758 * Additional semantic data to be carried around with this Notification. 759 * <p> 760 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 761 * APIs, and are intended to be used by 762 * {@link android.service.notification.NotificationListenerService} implementations to extract 763 * detailed information from notification objects. 764 */ 765 public Bundle extras = new Bundle(); 766 767 /** 768 * All pending intents in the notification as the system needs to be able to access them but 769 * touching the extras bundle in the system process is not safe because the bundle may contain 770 * custom parcelable objects. 771 * 772 * @hide 773 */ 774 public ArraySet<PendingIntent> allPendingIntents; 775 776 /** 777 * {@link #extras} key: this is the title of the notification, 778 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 779 */ 780 public static final String EXTRA_TITLE = "android.title"; 781 782 /** 783 * {@link #extras} key: this is the title of the notification when shown in expanded form, 784 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 785 */ 786 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 787 788 /** 789 * {@link #extras} key: this is the main text payload, as supplied to 790 * {@link Builder#setContentText(CharSequence)}. 791 */ 792 public static final String EXTRA_TEXT = "android.text"; 793 794 /** 795 * {@link #extras} key: this is a third line of text, as supplied to 796 * {@link Builder#setSubText(CharSequence)}. 797 */ 798 public static final String EXTRA_SUB_TEXT = "android.subText"; 799 800 /** 801 * {@link #extras} key: this is the remote input history, as supplied to 802 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 803 * 804 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 805 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 806 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 807 * notifications once the other party has responded). 808 * 809 * The extra with this key is of type CharSequence[] and contains the most recent entry at 810 * the 0 index, the second most recent at the 1 index, etc. 811 * 812 * @see Builder#setRemoteInputHistory(CharSequence[]) 813 */ 814 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 815 816 /** 817 * {@link #extras} key: this is a small piece of additional text as supplied to 818 * {@link Builder#setContentInfo(CharSequence)}. 819 */ 820 public static final String EXTRA_INFO_TEXT = "android.infoText"; 821 822 /** 823 * {@link #extras} key: this is a line of summary information intended to be shown 824 * alongside expanded notifications, as supplied to (e.g.) 825 * {@link BigTextStyle#setSummaryText(CharSequence)}. 826 */ 827 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 828 829 /** 830 * {@link #extras} key: this is the longer text shown in the big form of a 831 * {@link BigTextStyle} notification, as supplied to 832 * {@link BigTextStyle#bigText(CharSequence)}. 833 */ 834 public static final String EXTRA_BIG_TEXT = "android.bigText"; 835 836 /** 837 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 838 * supplied to {@link Builder#setSmallIcon(int)}. 839 */ 840 public static final String EXTRA_SMALL_ICON = "android.icon"; 841 842 /** 843 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 844 * notification payload, as 845 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 846 */ 847 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 848 849 /** 850 * {@link #extras} key: this is a bitmap to be used instead of the one from 851 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 852 * shown in its expanded form, as supplied to 853 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 854 */ 855 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 856 857 /** 858 * {@link #extras} key: this is the progress value supplied to 859 * {@link Builder#setProgress(int, int, boolean)}. 860 */ 861 public static final String EXTRA_PROGRESS = "android.progress"; 862 863 /** 864 * {@link #extras} key: this is the maximum value supplied to 865 * {@link Builder#setProgress(int, int, boolean)}. 866 */ 867 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 868 869 /** 870 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 871 * {@link Builder#setProgress(int, int, boolean)}. 872 */ 873 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 874 875 /** 876 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 877 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 878 * {@link Builder#setUsesChronometer(boolean)}. 879 */ 880 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 881 882 /** 883 * {@link #extras} key: whether the chronometer set on the notification should count down 884 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 885 * This extra is a boolean. The default is false. 886 */ 887 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 888 889 /** 890 * {@link #extras} key: whether {@link #when} should be shown, 891 * as supplied to {@link Builder#setShowWhen(boolean)}. 892 */ 893 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 894 895 /** 896 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 897 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 898 */ 899 public static final String EXTRA_PICTURE = "android.picture"; 900 901 /** 902 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 903 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 904 */ 905 public static final String EXTRA_TEXT_LINES = "android.textLines"; 906 907 /** 908 * {@link #extras} key: A string representing the name of the specific 909 * {@link android.app.Notification.Style} used to create this notification. 910 */ 911 public static final String EXTRA_TEMPLATE = "android.template"; 912 913 /** 914 * {@link #extras} key: A String array containing the people that this notification relates to, 915 * each of which was supplied to {@link Builder#addPerson(String)}. 916 */ 917 public static final String EXTRA_PEOPLE = "android.people"; 918 919 /** 920 * Allow certain system-generated notifications to appear before the device is provisioned. 921 * Only available to notifications coming from the android package. 922 * @hide 923 */ 924 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 925 926 /** 927 * {@link #extras} key: A 928 * {@link android.content.ContentUris content URI} pointing to an image that can be displayed 929 * in the background when the notification is selected. The URI must point to an image stream 930 * suitable for passing into 931 * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 932 * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider 933 * URI used for this purpose must require no permissions to read the image data. 934 */ 935 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 936 937 /** 938 * {@link #extras} key: A 939 * {@link android.media.session.MediaSession.Token} associated with a 940 * {@link android.app.Notification.MediaStyle} notification. 941 */ 942 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 943 944 /** 945 * {@link #extras} key: the indices of actions to be shown in the compact view, 946 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 947 */ 948 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 949 950 /** 951 * {@link #extras} key: the username to be displayed for all messages sent by the user including 952 * direct replies 953 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 954 * {@link CharSequence} 955 */ 956 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 957 958 /** 959 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 960 * represented by a {@link android.app.Notification.MessagingStyle} 961 */ 962 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 963 964 /** 965 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 966 * bundles provided by a 967 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 968 * array of bundles. 969 */ 970 public static final String EXTRA_MESSAGES = "android.messages"; 971 972 /** 973 * {@link #extras} key: the user that built the notification. 974 * 975 * @hide 976 */ 977 public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId"; 978 979 /** 980 * @hide 981 */ 982 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 983 984 /** 985 * @hide 986 */ 987 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 988 989 /** @hide */ 990 @SystemApi 991 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 992 993 private Icon mSmallIcon; 994 private Icon mLargeIcon; 995 996 private String mChannelId; 997 998 /** 999 * Structure to encapsulate a named action that can be shown as part of this notification. 1000 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1001 * selected by the user. 1002 * <p> 1003 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1004 * or {@link Notification.Builder#addAction(Notification.Action)} 1005 * to attach actions. 1006 */ 1007 public static class Action implements Parcelable { 1008 private final Bundle mExtras; 1009 private Icon mIcon; 1010 private final RemoteInput[] mRemoteInputs; 1011 private boolean mAllowGeneratedReplies = false; 1012 1013 /** 1014 * Small icon representing the action. 1015 * 1016 * @deprecated Use {@link Action#getIcon()} instead. 1017 */ 1018 @Deprecated 1019 public int icon; 1020 1021 /** 1022 * Title of the action. 1023 */ 1024 public CharSequence title; 1025 1026 /** 1027 * Intent to send when the user invokes this action. May be null, in which case the action 1028 * may be rendered in a disabled presentation by the system UI. 1029 */ 1030 public PendingIntent actionIntent; 1031 1032 private Action(Parcel in) { 1033 if (in.readInt() != 0) { 1034 mIcon = Icon.CREATOR.createFromParcel(in); 1035 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1036 icon = mIcon.getResId(); 1037 } 1038 } 1039 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1040 if (in.readInt() == 1) { 1041 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1042 } 1043 mExtras = Bundle.setDefusable(in.readBundle(), true); 1044 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1045 mAllowGeneratedReplies = in.readInt() == 1; 1046 } 1047 1048 /** 1049 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1050 */ 1051 @Deprecated 1052 public Action(int icon, CharSequence title, PendingIntent intent) { 1053 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, false); 1054 } 1055 1056 /** Keep in sync with {@link Notification.Action.Builder#Builder(Action)}! */ 1057 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1058 RemoteInput[] remoteInputs, boolean allowGeneratedReplies) { 1059 this.mIcon = icon; 1060 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1061 this.icon = icon.getResId(); 1062 } 1063 this.title = title; 1064 this.actionIntent = intent; 1065 this.mExtras = extras != null ? extras : new Bundle(); 1066 this.mRemoteInputs = remoteInputs; 1067 this.mAllowGeneratedReplies = allowGeneratedReplies; 1068 } 1069 1070 /** 1071 * Return an icon representing the action. 1072 */ 1073 public Icon getIcon() { 1074 if (mIcon == null && icon != 0) { 1075 // you snuck an icon in here without using the builder; let's try to keep it 1076 mIcon = Icon.createWithResource("", icon); 1077 } 1078 return mIcon; 1079 } 1080 1081 /** 1082 * Get additional metadata carried around with this Action. 1083 */ 1084 public Bundle getExtras() { 1085 return mExtras; 1086 } 1087 1088 /** 1089 * Return whether the platform should automatically generate possible replies for this 1090 * {@link Action} 1091 */ 1092 public boolean getAllowGeneratedReplies() { 1093 return mAllowGeneratedReplies; 1094 } 1095 1096 /** 1097 * Get the list of inputs to be collected from the user when this action is sent. 1098 * May return null if no remote inputs were added. 1099 */ 1100 public RemoteInput[] getRemoteInputs() { 1101 return mRemoteInputs; 1102 } 1103 1104 /** 1105 * Builder class for {@link Action} objects. 1106 */ 1107 public static final class Builder { 1108 private final Icon mIcon; 1109 private final CharSequence mTitle; 1110 private final PendingIntent mIntent; 1111 private boolean mAllowGeneratedReplies; 1112 private final Bundle mExtras; 1113 private ArrayList<RemoteInput> mRemoteInputs; 1114 1115 /** 1116 * Construct a new builder for {@link Action} object. 1117 * @param icon icon to show for this action 1118 * @param title the title of the action 1119 * @param intent the {@link PendingIntent} to fire when users trigger this action 1120 */ 1121 @Deprecated 1122 public Builder(int icon, CharSequence title, PendingIntent intent) { 1123 this(Icon.createWithResource("", icon), title, intent); 1124 } 1125 1126 /** 1127 * Construct a new builder for {@link Action} object. 1128 * @param icon icon to show for this action 1129 * @param title the title of the action 1130 * @param intent the {@link PendingIntent} to fire when users trigger this action 1131 */ 1132 public Builder(Icon icon, CharSequence title, PendingIntent intent) { 1133 this(icon, title, intent, new Bundle(), null, false); 1134 } 1135 1136 /** 1137 * Construct a new builder for {@link Action} object using the fields from an 1138 * {@link Action}. 1139 * @param action the action to read fields from. 1140 */ 1141 public Builder(Action action) { 1142 this(action.getIcon(), action.title, action.actionIntent, 1143 new Bundle(action.mExtras), action.getRemoteInputs(), 1144 action.getAllowGeneratedReplies()); 1145 } 1146 1147 private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1148 RemoteInput[] remoteInputs, boolean allowGeneratedReplies) { 1149 mIcon = icon; 1150 mTitle = title; 1151 mIntent = intent; 1152 mExtras = extras; 1153 if (remoteInputs != null) { 1154 mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length); 1155 Collections.addAll(mRemoteInputs, remoteInputs); 1156 } 1157 mAllowGeneratedReplies = allowGeneratedReplies; 1158 } 1159 1160 /** 1161 * Merge additional metadata into this builder. 1162 * 1163 * <p>Values within the Bundle will replace existing extras values in this Builder. 1164 * 1165 * @see Notification.Action#extras 1166 */ 1167 public Builder addExtras(Bundle extras) { 1168 if (extras != null) { 1169 mExtras.putAll(extras); 1170 } 1171 return this; 1172 } 1173 1174 /** 1175 * Get the metadata Bundle used by this Builder. 1176 * 1177 * <p>The returned Bundle is shared with this Builder. 1178 */ 1179 public Bundle getExtras() { 1180 return mExtras; 1181 } 1182 1183 /** 1184 * Add an input to be collected from the user when this action is sent. 1185 * Response values can be retrieved from the fired intent by using the 1186 * {@link RemoteInput#getResultsFromIntent} function. 1187 * @param remoteInput a {@link RemoteInput} to add to the action 1188 * @return this object for method chaining 1189 */ 1190 public Builder addRemoteInput(RemoteInput remoteInput) { 1191 if (mRemoteInputs == null) { 1192 mRemoteInputs = new ArrayList<RemoteInput>(); 1193 } 1194 mRemoteInputs.add(remoteInput); 1195 return this; 1196 } 1197 1198 /** 1199 * Set whether the platform should automatically generate possible replies to add to 1200 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 1201 * {@link RemoteInput}, this has no effect. 1202 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 1203 * otherwise 1204 * @return this object for method chaining 1205 * The default value is {@code false} 1206 */ 1207 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 1208 mAllowGeneratedReplies = allowGeneratedReplies; 1209 return this; 1210 } 1211 1212 /** 1213 * Apply an extender to this action builder. Extenders may be used to add 1214 * metadata or change options on this builder. 1215 */ 1216 public Builder extend(Extender extender) { 1217 extender.extend(this); 1218 return this; 1219 } 1220 1221 /** 1222 * Combine all of the options that have been set and return a new {@link Action} 1223 * object. 1224 * @return the built action 1225 */ 1226 public Action build() { 1227 RemoteInput[] remoteInputs = mRemoteInputs != null 1228 ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null; 1229 return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs, 1230 mAllowGeneratedReplies); 1231 } 1232 } 1233 1234 @Override 1235 public Action clone() { 1236 return new Action( 1237 getIcon(), 1238 title, 1239 actionIntent, // safe to alias 1240 mExtras == null ? new Bundle() : new Bundle(mExtras), 1241 getRemoteInputs(), 1242 getAllowGeneratedReplies()); 1243 } 1244 @Override 1245 public int describeContents() { 1246 return 0; 1247 } 1248 @Override 1249 public void writeToParcel(Parcel out, int flags) { 1250 final Icon ic = getIcon(); 1251 if (ic != null) { 1252 out.writeInt(1); 1253 ic.writeToParcel(out, 0); 1254 } else { 1255 out.writeInt(0); 1256 } 1257 TextUtils.writeToParcel(title, out, flags); 1258 if (actionIntent != null) { 1259 out.writeInt(1); 1260 actionIntent.writeToParcel(out, flags); 1261 } else { 1262 out.writeInt(0); 1263 } 1264 out.writeBundle(mExtras); 1265 out.writeTypedArray(mRemoteInputs, flags); 1266 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 1267 } 1268 public static final Parcelable.Creator<Action> CREATOR = 1269 new Parcelable.Creator<Action>() { 1270 public Action createFromParcel(Parcel in) { 1271 return new Action(in); 1272 } 1273 public Action[] newArray(int size) { 1274 return new Action[size]; 1275 } 1276 }; 1277 1278 /** 1279 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 1280 * metadata or change options on an action builder. 1281 */ 1282 public interface Extender { 1283 /** 1284 * Apply this extender to a notification action builder. 1285 * @param builder the builder to be modified. 1286 * @return the build object for chaining. 1287 */ 1288 public Builder extend(Builder builder); 1289 } 1290 1291 /** 1292 * Wearable extender for notification actions. To add extensions to an action, 1293 * create a new {@link android.app.Notification.Action.WearableExtender} object using 1294 * the {@code WearableExtender()} constructor and apply it to a 1295 * {@link android.app.Notification.Action.Builder} using 1296 * {@link android.app.Notification.Action.Builder#extend}. 1297 * 1298 * <pre class="prettyprint"> 1299 * Notification.Action action = new Notification.Action.Builder( 1300 * R.drawable.archive_all, "Archive all", actionIntent) 1301 * .extend(new Notification.Action.WearableExtender() 1302 * .setAvailableOffline(false)) 1303 * .build();</pre> 1304 */ 1305 public static final class WearableExtender implements Extender { 1306 /** Notification action extra which contains wearable extensions */ 1307 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 1308 1309 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 1310 private static final String KEY_FLAGS = "flags"; 1311 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 1312 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 1313 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 1314 1315 // Flags bitwise-ored to mFlags 1316 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 1317 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 1318 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 1319 1320 // Default value for flags integer 1321 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 1322 1323 private int mFlags = DEFAULT_FLAGS; 1324 1325 private CharSequence mInProgressLabel; 1326 private CharSequence mConfirmLabel; 1327 private CharSequence mCancelLabel; 1328 1329 /** 1330 * Create a {@link android.app.Notification.Action.WearableExtender} with default 1331 * options. 1332 */ 1333 public WearableExtender() { 1334 } 1335 1336 /** 1337 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 1338 * wearable options present in an existing notification action. 1339 * @param action the notification action to inspect. 1340 */ 1341 public WearableExtender(Action action) { 1342 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 1343 if (wearableBundle != null) { 1344 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 1345 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 1346 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 1347 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 1348 } 1349 } 1350 1351 /** 1352 * Apply wearable extensions to a notification action that is being built. This is 1353 * typically called by the {@link android.app.Notification.Action.Builder#extend} 1354 * method of {@link android.app.Notification.Action.Builder}. 1355 */ 1356 @Override 1357 public Action.Builder extend(Action.Builder builder) { 1358 Bundle wearableBundle = new Bundle(); 1359 1360 if (mFlags != DEFAULT_FLAGS) { 1361 wearableBundle.putInt(KEY_FLAGS, mFlags); 1362 } 1363 if (mInProgressLabel != null) { 1364 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 1365 } 1366 if (mConfirmLabel != null) { 1367 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 1368 } 1369 if (mCancelLabel != null) { 1370 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 1371 } 1372 1373 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 1374 return builder; 1375 } 1376 1377 @Override 1378 public WearableExtender clone() { 1379 WearableExtender that = new WearableExtender(); 1380 that.mFlags = this.mFlags; 1381 that.mInProgressLabel = this.mInProgressLabel; 1382 that.mConfirmLabel = this.mConfirmLabel; 1383 that.mCancelLabel = this.mCancelLabel; 1384 return that; 1385 } 1386 1387 /** 1388 * Set whether this action is available when the wearable device is not connected to 1389 * a companion device. The user can still trigger this action when the wearable device is 1390 * offline, but a visual hint will indicate that the action may not be available. 1391 * Defaults to true. 1392 */ 1393 public WearableExtender setAvailableOffline(boolean availableOffline) { 1394 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 1395 return this; 1396 } 1397 1398 /** 1399 * Get whether this action is available when the wearable device is not connected to 1400 * a companion device. The user can still trigger this action when the wearable device is 1401 * offline, but a visual hint will indicate that the action may not be available. 1402 * Defaults to true. 1403 */ 1404 public boolean isAvailableOffline() { 1405 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 1406 } 1407 1408 private void setFlag(int mask, boolean value) { 1409 if (value) { 1410 mFlags |= mask; 1411 } else { 1412 mFlags &= ~mask; 1413 } 1414 } 1415 1416 /** 1417 * Set a label to display while the wearable is preparing to automatically execute the 1418 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 1419 * 1420 * @param label the label to display while the action is being prepared to execute 1421 * @return this object for method chaining 1422 */ 1423 public WearableExtender setInProgressLabel(CharSequence label) { 1424 mInProgressLabel = label; 1425 return this; 1426 } 1427 1428 /** 1429 * Get the label to display while the wearable is preparing to automatically execute 1430 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 1431 * 1432 * @return the label to display while the action is being prepared to execute 1433 */ 1434 public CharSequence getInProgressLabel() { 1435 return mInProgressLabel; 1436 } 1437 1438 /** 1439 * Set a label to display to confirm that the action should be executed. 1440 * This is usually an imperative verb like "Send". 1441 * 1442 * @param label the label to confirm the action should be executed 1443 * @return this object for method chaining 1444 */ 1445 public WearableExtender setConfirmLabel(CharSequence label) { 1446 mConfirmLabel = label; 1447 return this; 1448 } 1449 1450 /** 1451 * Get the label to display to confirm that the action should be executed. 1452 * This is usually an imperative verb like "Send". 1453 * 1454 * @return the label to confirm the action should be executed 1455 */ 1456 public CharSequence getConfirmLabel() { 1457 return mConfirmLabel; 1458 } 1459 1460 /** 1461 * Set a label to display to cancel the action. 1462 * This is usually an imperative verb, like "Cancel". 1463 * 1464 * @param label the label to display to cancel the action 1465 * @return this object for method chaining 1466 */ 1467 public WearableExtender setCancelLabel(CharSequence label) { 1468 mCancelLabel = label; 1469 return this; 1470 } 1471 1472 /** 1473 * Get the label to display to cancel the action. 1474 * This is usually an imperative verb like "Cancel". 1475 * 1476 * @return the label to display to cancel the action 1477 */ 1478 public CharSequence getCancelLabel() { 1479 return mCancelLabel; 1480 } 1481 1482 /** 1483 * Set a hint that this Action will launch an {@link Activity} directly, telling the 1484 * platform that it can generate the appropriate transitions. 1485 * @param hintLaunchesActivity {@code true} if the content intent will launch 1486 * an activity and transitions should be generated, false otherwise. 1487 * @return this object for method chaining 1488 */ 1489 public WearableExtender setHintLaunchesActivity( 1490 boolean hintLaunchesActivity) { 1491 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 1492 return this; 1493 } 1494 1495 /** 1496 * Get a hint that this Action will launch an {@link Activity} directly, telling the 1497 * platform that it can generate the appropriate transitions 1498 * @return {@code true} if the content intent will launch an activity and transitions 1499 * should be generated, false otherwise. The default value is {@code false} if this was 1500 * never set. 1501 */ 1502 public boolean getHintLaunchesActivity() { 1503 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 1504 } 1505 1506 /** 1507 * Set a hint that this Action should be displayed inline. 1508 * 1509 * @param hintDisplayInline {@code true} if action should be displayed inline, false 1510 * otherwise 1511 * @return this object for method chaining 1512 */ 1513 public WearableExtender setHintDisplayActionInline( 1514 boolean hintDisplayInline) { 1515 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 1516 return this; 1517 } 1518 1519 /** 1520 * Get a hint that this Action should be displayed inline. 1521 * 1522 * @return {@code true} if the Action should be displayed inline, {@code false} 1523 * otherwise. The default value is {@code false} if this was never set. 1524 */ 1525 public boolean getHintDisplayActionInline() { 1526 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 1527 } 1528 } 1529 } 1530 1531 /** 1532 * Array of all {@link Action} structures attached to this notification by 1533 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 1534 * {@link android.service.notification.NotificationListenerService} that provide an alternative 1535 * interface for invoking actions. 1536 */ 1537 public Action[] actions; 1538 1539 /** 1540 * Replacement version of this notification whose content will be shown 1541 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 1542 * and {@link #VISIBILITY_PUBLIC}. 1543 */ 1544 public Notification publicVersion; 1545 1546 /** 1547 * Constructs a Notification object with default values. 1548 * You might want to consider using {@link Builder} instead. 1549 */ 1550 public Notification() 1551 { 1552 this.when = System.currentTimeMillis(); 1553 this.creationTime = System.currentTimeMillis(); 1554 this.priority = PRIORITY_DEFAULT; 1555 } 1556 1557 /** 1558 * @hide 1559 */ 1560 public Notification(Context context, int icon, CharSequence tickerText, long when, 1561 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 1562 { 1563 new Builder(context) 1564 .setWhen(when) 1565 .setSmallIcon(icon) 1566 .setTicker(tickerText) 1567 .setContentTitle(contentTitle) 1568 .setContentText(contentText) 1569 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) 1570 .buildInto(this); 1571 } 1572 1573 /** 1574 * Constructs a Notification object with the information needed to 1575 * have a status bar icon without the standard expanded view. 1576 * 1577 * @param icon The resource id of the icon to put in the status bar. 1578 * @param tickerText The text that flows by in the status bar when the notification first 1579 * activates. 1580 * @param when The time to show in the time field. In the System.currentTimeMillis 1581 * timebase. 1582 * 1583 * @deprecated Use {@link Builder} instead. 1584 */ 1585 @Deprecated 1586 public Notification(int icon, CharSequence tickerText, long when) 1587 { 1588 this.icon = icon; 1589 this.tickerText = tickerText; 1590 this.when = when; 1591 this.creationTime = System.currentTimeMillis(); 1592 } 1593 1594 /** 1595 * Unflatten the notification from a parcel. 1596 */ 1597 @SuppressWarnings("unchecked") 1598 public Notification(Parcel parcel) { 1599 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 1600 // intents in extras are always written as the last entry. 1601 readFromParcelImpl(parcel); 1602 // Must be read last! 1603 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 1604 } 1605 1606 private void readFromParcelImpl(Parcel parcel) 1607 { 1608 int version = parcel.readInt(); 1609 1610 when = parcel.readLong(); 1611 creationTime = parcel.readLong(); 1612 if (parcel.readInt() != 0) { 1613 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 1614 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 1615 icon = mSmallIcon.getResId(); 1616 } 1617 } 1618 number = parcel.readInt(); 1619 if (parcel.readInt() != 0) { 1620 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 1621 } 1622 if (parcel.readInt() != 0) { 1623 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 1624 } 1625 if (parcel.readInt() != 0) { 1626 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 1627 } 1628 if (parcel.readInt() != 0) { 1629 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 1630 } 1631 if (parcel.readInt() != 0) { 1632 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 1633 } 1634 if (parcel.readInt() != 0) { 1635 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 1636 } 1637 defaults = parcel.readInt(); 1638 flags = parcel.readInt(); 1639 if (parcel.readInt() != 0) { 1640 sound = Uri.CREATOR.createFromParcel(parcel); 1641 } 1642 1643 audioStreamType = parcel.readInt(); 1644 if (parcel.readInt() != 0) { 1645 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 1646 } 1647 vibrate = parcel.createLongArray(); 1648 ledARGB = parcel.readInt(); 1649 ledOnMS = parcel.readInt(); 1650 ledOffMS = parcel.readInt(); 1651 iconLevel = parcel.readInt(); 1652 1653 if (parcel.readInt() != 0) { 1654 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 1655 } 1656 1657 priority = parcel.readInt(); 1658 1659 category = parcel.readString(); 1660 1661 mGroupKey = parcel.readString(); 1662 1663 mSortKey = parcel.readString(); 1664 1665 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 1666 1667 actions = parcel.createTypedArray(Action.CREATOR); // may be null 1668 1669 if (parcel.readInt() != 0) { 1670 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 1671 } 1672 1673 if (parcel.readInt() != 0) { 1674 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 1675 } 1676 1677 visibility = parcel.readInt(); 1678 1679 if (parcel.readInt() != 0) { 1680 publicVersion = Notification.CREATOR.createFromParcel(parcel); 1681 } 1682 1683 color = parcel.readInt(); 1684 1685 if (parcel.readInt() != 0) { 1686 mChannelId = parcel.readString(); 1687 } 1688 } 1689 1690 @Override 1691 public Notification clone() { 1692 Notification that = new Notification(); 1693 cloneInto(that, true); 1694 return that; 1695 } 1696 1697 /** 1698 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 1699 * of this into that. 1700 * @hide 1701 */ 1702 public void cloneInto(Notification that, boolean heavy) { 1703 that.when = this.when; 1704 that.creationTime = this.creationTime; 1705 that.mSmallIcon = this.mSmallIcon; 1706 that.number = this.number; 1707 1708 // PendingIntents are global, so there's no reason (or way) to clone them. 1709 that.contentIntent = this.contentIntent; 1710 that.deleteIntent = this.deleteIntent; 1711 that.fullScreenIntent = this.fullScreenIntent; 1712 1713 if (this.tickerText != null) { 1714 that.tickerText = this.tickerText.toString(); 1715 } 1716 if (heavy && this.tickerView != null) { 1717 that.tickerView = this.tickerView.clone(); 1718 } 1719 if (heavy && this.contentView != null) { 1720 that.contentView = this.contentView.clone(); 1721 } 1722 if (heavy && this.mLargeIcon != null) { 1723 that.mLargeIcon = this.mLargeIcon; 1724 } 1725 that.iconLevel = this.iconLevel; 1726 that.sound = this.sound; // android.net.Uri is immutable 1727 that.audioStreamType = this.audioStreamType; 1728 if (this.audioAttributes != null) { 1729 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 1730 } 1731 1732 final long[] vibrate = this.vibrate; 1733 if (vibrate != null) { 1734 final int N = vibrate.length; 1735 final long[] vib = that.vibrate = new long[N]; 1736 System.arraycopy(vibrate, 0, vib, 0, N); 1737 } 1738 1739 that.ledARGB = this.ledARGB; 1740 that.ledOnMS = this.ledOnMS; 1741 that.ledOffMS = this.ledOffMS; 1742 that.defaults = this.defaults; 1743 1744 that.flags = this.flags; 1745 1746 that.priority = this.priority; 1747 1748 that.category = this.category; 1749 1750 that.mGroupKey = this.mGroupKey; 1751 1752 that.mSortKey = this.mSortKey; 1753 1754 if (this.extras != null) { 1755 try { 1756 that.extras = new Bundle(this.extras); 1757 // will unparcel 1758 that.extras.size(); 1759 } catch (BadParcelableException e) { 1760 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 1761 that.extras = null; 1762 } 1763 } 1764 1765 if (!ArrayUtils.isEmpty(allPendingIntents)) { 1766 that.allPendingIntents = new ArraySet<>(allPendingIntents); 1767 } 1768 1769 if (this.actions != null) { 1770 that.actions = new Action[this.actions.length]; 1771 for(int i=0; i<this.actions.length; i++) { 1772 that.actions[i] = this.actions[i].clone(); 1773 } 1774 } 1775 1776 if (heavy && this.bigContentView != null) { 1777 that.bigContentView = this.bigContentView.clone(); 1778 } 1779 1780 if (heavy && this.headsUpContentView != null) { 1781 that.headsUpContentView = this.headsUpContentView.clone(); 1782 } 1783 1784 that.visibility = this.visibility; 1785 1786 if (this.publicVersion != null) { 1787 that.publicVersion = new Notification(); 1788 this.publicVersion.cloneInto(that.publicVersion, heavy); 1789 } 1790 1791 that.color = this.color; 1792 1793 that.mChannelId = this.mChannelId; 1794 1795 if (!heavy) { 1796 that.lightenPayload(); // will clean out extras 1797 } 1798 } 1799 1800 /** 1801 * Removes heavyweight parts of the Notification object for archival or for sending to 1802 * listeners when the full contents are not necessary. 1803 * @hide 1804 */ 1805 public final void lightenPayload() { 1806 tickerView = null; 1807 contentView = null; 1808 bigContentView = null; 1809 headsUpContentView = null; 1810 mLargeIcon = null; 1811 if (extras != null && !extras.isEmpty()) { 1812 final Set<String> keyset = extras.keySet(); 1813 final int N = keyset.size(); 1814 final String[] keys = keyset.toArray(new String[N]); 1815 for (int i=0; i<N; i++) { 1816 final String key = keys[i]; 1817 final Object obj = extras.get(key); 1818 if (obj != null && 1819 ( obj instanceof Parcelable 1820 || obj instanceof Parcelable[] 1821 || obj instanceof SparseArray 1822 || obj instanceof ArrayList)) { 1823 extras.remove(key); 1824 } 1825 } 1826 } 1827 } 1828 1829 /** 1830 * Make sure this CharSequence is safe to put into a bundle, which basically 1831 * means it had better not be some custom Parcelable implementation. 1832 * @hide 1833 */ 1834 public static CharSequence safeCharSequence(CharSequence cs) { 1835 if (cs == null) return cs; 1836 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 1837 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 1838 } 1839 if (cs instanceof Parcelable) { 1840 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 1841 + " instance is a custom Parcelable and not allowed in Notification"); 1842 return cs.toString(); 1843 } 1844 return removeTextSizeSpans(cs); 1845 } 1846 1847 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 1848 if (charSequence instanceof Spanned) { 1849 Spanned ss = (Spanned) charSequence; 1850 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 1851 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 1852 for (Object span : spans) { 1853 Object resultSpan = span; 1854 if (resultSpan instanceof CharacterStyle) { 1855 resultSpan = ((CharacterStyle) span).getUnderlying(); 1856 } 1857 if (resultSpan instanceof TextAppearanceSpan) { 1858 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 1859 resultSpan = new TextAppearanceSpan( 1860 originalSpan.getFamily(), 1861 originalSpan.getTextStyle(), 1862 -1, 1863 originalSpan.getTextColor(), 1864 originalSpan.getLinkTextColor()); 1865 } else if (resultSpan instanceof RelativeSizeSpan 1866 || resultSpan instanceof AbsoluteSizeSpan) { 1867 continue; 1868 } else { 1869 resultSpan = span; 1870 } 1871 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 1872 ss.getSpanFlags(span)); 1873 } 1874 return builder; 1875 } 1876 return charSequence; 1877 } 1878 1879 public int describeContents() { 1880 return 0; 1881 } 1882 1883 /** 1884 * Flatten this notification into a parcel. 1885 */ 1886 public void writeToParcel(Parcel parcel, int flags) { 1887 // We need to mark all pending intents getting into the notification 1888 // system as being put there to later allow the notification ranker 1889 // to launch them and by doing so add the app to the battery saver white 1890 // list for a short period of time. The problem is that the system 1891 // cannot look into the extras as there may be parcelables there that 1892 // the platform does not know how to handle. To go around that we have 1893 // an explicit list of the pending intents in the extras bundle. 1894 final boolean collectPendingIntents = (allPendingIntents == null); 1895 if (collectPendingIntents) { 1896 PendingIntent.setOnMarshaledListener( 1897 (PendingIntent intent, Parcel out, int outFlags) -> { 1898 if (parcel == out) { 1899 if (allPendingIntents == null) { 1900 allPendingIntents = new ArraySet<>(); 1901 } 1902 allPendingIntents.add(intent); 1903 } 1904 }); 1905 } 1906 try { 1907 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 1908 // want to intercept all pending events written to the pacel. 1909 writeToParcelImpl(parcel, flags); 1910 // Must be written last! 1911 parcel.writeArraySet(allPendingIntents); 1912 } finally { 1913 if (collectPendingIntents) { 1914 PendingIntent.setOnMarshaledListener(null); 1915 } 1916 } 1917 } 1918 1919 private void writeToParcelImpl(Parcel parcel, int flags) { 1920 parcel.writeInt(1); 1921 1922 parcel.writeLong(when); 1923 parcel.writeLong(creationTime); 1924 if (mSmallIcon == null && icon != 0) { 1925 // you snuck an icon in here without using the builder; let's try to keep it 1926 mSmallIcon = Icon.createWithResource("", icon); 1927 } 1928 if (mSmallIcon != null) { 1929 parcel.writeInt(1); 1930 mSmallIcon.writeToParcel(parcel, 0); 1931 } else { 1932 parcel.writeInt(0); 1933 } 1934 parcel.writeInt(number); 1935 if (contentIntent != null) { 1936 parcel.writeInt(1); 1937 contentIntent.writeToParcel(parcel, 0); 1938 } else { 1939 parcel.writeInt(0); 1940 } 1941 if (deleteIntent != null) { 1942 parcel.writeInt(1); 1943 deleteIntent.writeToParcel(parcel, 0); 1944 } else { 1945 parcel.writeInt(0); 1946 } 1947 if (tickerText != null) { 1948 parcel.writeInt(1); 1949 TextUtils.writeToParcel(tickerText, parcel, flags); 1950 } else { 1951 parcel.writeInt(0); 1952 } 1953 if (tickerView != null) { 1954 parcel.writeInt(1); 1955 tickerView.writeToParcel(parcel, 0); 1956 } else { 1957 parcel.writeInt(0); 1958 } 1959 if (contentView != null) { 1960 parcel.writeInt(1); 1961 contentView.writeToParcel(parcel, 0); 1962 } else { 1963 parcel.writeInt(0); 1964 } 1965 if (mLargeIcon == null && largeIcon != null) { 1966 // you snuck an icon in here without using the builder; let's try to keep it 1967 mLargeIcon = Icon.createWithBitmap(largeIcon); 1968 } 1969 if (mLargeIcon != null) { 1970 parcel.writeInt(1); 1971 mLargeIcon.writeToParcel(parcel, 0); 1972 } else { 1973 parcel.writeInt(0); 1974 } 1975 1976 parcel.writeInt(defaults); 1977 parcel.writeInt(this.flags); 1978 1979 if (sound != null) { 1980 parcel.writeInt(1); 1981 sound.writeToParcel(parcel, 0); 1982 } else { 1983 parcel.writeInt(0); 1984 } 1985 parcel.writeInt(audioStreamType); 1986 1987 if (audioAttributes != null) { 1988 parcel.writeInt(1); 1989 audioAttributes.writeToParcel(parcel, 0); 1990 } else { 1991 parcel.writeInt(0); 1992 } 1993 1994 parcel.writeLongArray(vibrate); 1995 parcel.writeInt(ledARGB); 1996 parcel.writeInt(ledOnMS); 1997 parcel.writeInt(ledOffMS); 1998 parcel.writeInt(iconLevel); 1999 2000 if (fullScreenIntent != null) { 2001 parcel.writeInt(1); 2002 fullScreenIntent.writeToParcel(parcel, 0); 2003 } else { 2004 parcel.writeInt(0); 2005 } 2006 2007 parcel.writeInt(priority); 2008 2009 parcel.writeString(category); 2010 2011 parcel.writeString(mGroupKey); 2012 2013 parcel.writeString(mSortKey); 2014 2015 parcel.writeBundle(extras); // null ok 2016 2017 parcel.writeTypedArray(actions, 0); // null ok 2018 2019 if (bigContentView != null) { 2020 parcel.writeInt(1); 2021 bigContentView.writeToParcel(parcel, 0); 2022 } else { 2023 parcel.writeInt(0); 2024 } 2025 2026 if (headsUpContentView != null) { 2027 parcel.writeInt(1); 2028 headsUpContentView.writeToParcel(parcel, 0); 2029 } else { 2030 parcel.writeInt(0); 2031 } 2032 2033 parcel.writeInt(visibility); 2034 2035 if (publicVersion != null) { 2036 parcel.writeInt(1); 2037 publicVersion.writeToParcel(parcel, 0); 2038 } else { 2039 parcel.writeInt(0); 2040 } 2041 2042 parcel.writeInt(color); 2043 2044 if (mChannelId != null) { 2045 parcel.writeInt(1); 2046 parcel.writeString(mChannelId); 2047 } else { 2048 parcel.writeInt(0); 2049 } 2050 } 2051 2052 /** 2053 * Parcelable.Creator that instantiates Notification objects 2054 */ 2055 public static final Parcelable.Creator<Notification> CREATOR 2056 = new Parcelable.Creator<Notification>() 2057 { 2058 public Notification createFromParcel(Parcel parcel) 2059 { 2060 return new Notification(parcel); 2061 } 2062 2063 public Notification[] newArray(int size) 2064 { 2065 return new Notification[size]; 2066 } 2067 }; 2068 2069 /** 2070 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 2071 * layout. 2072 * 2073 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 2074 * in the view.</p> 2075 * @param context The context for your application / activity. 2076 * @param contentTitle The title that goes in the expanded entry. 2077 * @param contentText The text that goes in the expanded entry. 2078 * @param contentIntent The intent to launch when the user clicks the expanded notification. 2079 * If this is an activity, it must include the 2080 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 2081 * that you take care of task management as described in the 2082 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 2083 * Stack</a> document. 2084 * 2085 * @deprecated Use {@link Builder} instead. 2086 * @removed 2087 */ 2088 @Deprecated 2089 public void setLatestEventInfo(Context context, 2090 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 2091 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 2092 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 2093 new Throwable()); 2094 } 2095 2096 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 2097 extras.putBoolean(EXTRA_SHOW_WHEN, true); 2098 } 2099 2100 // ensure that any information already set directly is preserved 2101 final Notification.Builder builder = new Notification.Builder(context, this); 2102 2103 // now apply the latestEventInfo fields 2104 if (contentTitle != null) { 2105 builder.setContentTitle(contentTitle); 2106 } 2107 if (contentText != null) { 2108 builder.setContentText(contentText); 2109 } 2110 builder.setContentIntent(contentIntent); 2111 2112 builder.build(); // callers expect this notification to be ready to use 2113 } 2114 2115 /** 2116 * @hide 2117 */ 2118 public static void addFieldsFromContext(Context context, Notification notification) { 2119 addFieldsFromContext(context.getApplicationInfo(), context.getUserId(), notification); 2120 } 2121 2122 /** 2123 * @hide 2124 */ 2125 public static void addFieldsFromContext(ApplicationInfo ai, int userId, 2126 Notification notification) { 2127 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 2128 notification.extras.putInt(EXTRA_ORIGINATING_USERID, userId); 2129 } 2130 2131 @Override 2132 public String toString() { 2133 StringBuilder sb = new StringBuilder(); 2134 sb.append("Notification(pri="); 2135 sb.append(priority); 2136 sb.append(" contentView="); 2137 if (contentView != null) { 2138 sb.append(contentView.getPackage()); 2139 sb.append("/0x"); 2140 sb.append(Integer.toHexString(contentView.getLayoutId())); 2141 } else { 2142 sb.append("null"); 2143 } 2144 sb.append(" vibrate="); 2145 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 2146 sb.append("default"); 2147 } else if (this.vibrate != null) { 2148 int N = this.vibrate.length-1; 2149 sb.append("["); 2150 for (int i=0; i<N; i++) { 2151 sb.append(this.vibrate[i]); 2152 sb.append(','); 2153 } 2154 if (N != -1) { 2155 sb.append(this.vibrate[N]); 2156 } 2157 sb.append("]"); 2158 } else { 2159 sb.append("null"); 2160 } 2161 sb.append(" sound="); 2162 if ((this.defaults & DEFAULT_SOUND) != 0) { 2163 sb.append("default"); 2164 } else if (this.sound != null) { 2165 sb.append(this.sound.toString()); 2166 } else { 2167 sb.append("null"); 2168 } 2169 if (this.tickerText != null) { 2170 sb.append(" tick"); 2171 } 2172 sb.append(" defaults=0x"); 2173 sb.append(Integer.toHexString(this.defaults)); 2174 sb.append(" flags=0x"); 2175 sb.append(Integer.toHexString(this.flags)); 2176 sb.append(String.format(" color=0x%08x", this.color)); 2177 if (this.category != null) { 2178 sb.append(" category="); 2179 sb.append(this.category); 2180 } 2181 if (this.mGroupKey != null) { 2182 sb.append(" groupKey="); 2183 sb.append(this.mGroupKey); 2184 } 2185 if (this.mSortKey != null) { 2186 sb.append(" sortKey="); 2187 sb.append(this.mSortKey); 2188 } 2189 if (actions != null) { 2190 sb.append(" actions="); 2191 sb.append(actions.length); 2192 } 2193 sb.append(" vis="); 2194 sb.append(visibilityToString(this.visibility)); 2195 if (this.publicVersion != null) { 2196 sb.append(" publicVersion="); 2197 sb.append(publicVersion.toString()); 2198 } 2199 sb.append(")"); 2200 return sb.toString(); 2201 } 2202 2203 /** 2204 * {@hide} 2205 */ 2206 public static String visibilityToString(int vis) { 2207 switch (vis) { 2208 case VISIBILITY_PRIVATE: 2209 return "PRIVATE"; 2210 case VISIBILITY_PUBLIC: 2211 return "PUBLIC"; 2212 case VISIBILITY_SECRET: 2213 return "SECRET"; 2214 default: 2215 return "UNKNOWN(" + String.valueOf(vis) + ")"; 2216 } 2217 } 2218 2219 /** 2220 * {@hide} 2221 */ 2222 public static String priorityToString(@Priority int pri) { 2223 switch (pri) { 2224 case PRIORITY_MIN: 2225 return "MIN"; 2226 case PRIORITY_LOW: 2227 return "LOW"; 2228 case PRIORITY_DEFAULT: 2229 return "DEFAULT"; 2230 case PRIORITY_HIGH: 2231 return "HIGH"; 2232 case PRIORITY_MAX: 2233 return "MAX"; 2234 default: 2235 return "UNKNOWN(" + String.valueOf(pri) + ")"; 2236 } 2237 } 2238 2239 /** 2240 * Returns the id of the channel this notification posts to. 2241 */ 2242 public String getChannel() { 2243 return mChannelId; 2244 } 2245 2246 /** 2247 * The small icon representing this notification in the status bar and content view. 2248 * 2249 * @return the small icon representing this notification. 2250 * 2251 * @see Builder#getSmallIcon() 2252 * @see Builder#setSmallIcon(Icon) 2253 */ 2254 public Icon getSmallIcon() { 2255 return mSmallIcon; 2256 } 2257 2258 /** 2259 * Used when notifying to clean up legacy small icons. 2260 * @hide 2261 */ 2262 public void setSmallIcon(Icon icon) { 2263 mSmallIcon = icon; 2264 } 2265 2266 /** 2267 * The large icon shown in this notification's content view. 2268 * @see Builder#getLargeIcon() 2269 * @see Builder#setLargeIcon(Icon) 2270 */ 2271 public Icon getLargeIcon() { 2272 return mLargeIcon; 2273 } 2274 2275 /** 2276 * @hide 2277 */ 2278 public boolean isGroupSummary() { 2279 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 2280 } 2281 2282 /** 2283 * @hide 2284 */ 2285 public boolean isGroupChild() { 2286 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 2287 } 2288 2289 /** 2290 * Builder class for {@link Notification} objects. 2291 * 2292 * Provides a convenient way to set the various fields of a {@link Notification} and generate 2293 * content views using the platform's notification layout template. If your app supports 2294 * versions of Android as old as API level 4, you can instead use 2295 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 2296 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 2297 * library</a>. 2298 * 2299 * <p>Example: 2300 * 2301 * <pre class="prettyprint"> 2302 * Notification noti = new Notification.Builder(mContext) 2303 * .setContentTitle("New mail from " + sender.toString()) 2304 * .setContentText(subject) 2305 * .setSmallIcon(R.drawable.new_mail) 2306 * .setLargeIcon(aBitmap) 2307 * .build(); 2308 * </pre> 2309 */ 2310 public static class Builder { 2311 /** 2312 * @hide 2313 */ 2314 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 2315 "android.rebuild.contentViewActionCount"; 2316 /** 2317 * @hide 2318 */ 2319 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 2320 = "android.rebuild.bigViewActionCount"; 2321 /** 2322 * @hide 2323 */ 2324 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 2325 = "android.rebuild.hudViewActionCount"; 2326 2327 private static final int MAX_ACTION_BUTTONS = 3; 2328 2329 private Context mContext; 2330 private Notification mN; 2331 private Bundle mUserExtras = new Bundle(); 2332 private Style mStyle; 2333 private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); 2334 private ArrayList<String> mPersonList = new ArrayList<String>(); 2335 private NotificationColorUtil mColorUtil; 2336 private boolean mColorUtilInited = false; 2337 2338 /** 2339 * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}. 2340 */ 2341 private int mCachedContrastColor = COLOR_INVALID; 2342 private int mCachedContrastColorIsFor = COLOR_INVALID; 2343 2344 /** 2345 * Constructs a new Builder with the defaults: 2346 * 2347 2348 * <table> 2349 * <tr><th align=right>priority</th> 2350 * <td>{@link #PRIORITY_DEFAULT}</td></tr> 2351 * <tr><th align=right>when</th> 2352 * <td>now ({@link System#currentTimeMillis()})</td></tr> 2353 * <tr><th align=right>audio stream</th> 2354 * <td>{@link #STREAM_DEFAULT}</td></tr> 2355 * </table> 2356 * 2357 2358 * @param context 2359 * A {@link Context} that will be used by the Builder to construct the 2360 * RemoteViews. The Context will not be held past the lifetime of this Builder 2361 * object. 2362 */ 2363 public Builder(Context context) { 2364 this(context, null); 2365 } 2366 2367 /** 2368 * @hide 2369 */ 2370 public Builder(Context context, Notification toAdopt) { 2371 mContext = context; 2372 2373 if (toAdopt == null) { 2374 mN = new Notification(); 2375 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 2376 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 2377 } 2378 mN.priority = PRIORITY_DEFAULT; 2379 mN.visibility = VISIBILITY_PRIVATE; 2380 } else { 2381 mN = toAdopt; 2382 if (mN.actions != null) { 2383 Collections.addAll(mActions, mN.actions); 2384 } 2385 2386 if (mN.extras.containsKey(EXTRA_PEOPLE)) { 2387 Collections.addAll(mPersonList, mN.extras.getStringArray(EXTRA_PEOPLE)); 2388 } 2389 2390 if (mN.getSmallIcon() == null && mN.icon != 0) { 2391 setSmallIcon(mN.icon); 2392 } 2393 2394 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 2395 setLargeIcon(mN.largeIcon); 2396 } 2397 2398 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 2399 if (!TextUtils.isEmpty(templateClass)) { 2400 final Class<? extends Style> styleClass 2401 = getNotificationStyleClass(templateClass); 2402 if (styleClass == null) { 2403 Log.d(TAG, "Unknown style class: " + templateClass); 2404 } else { 2405 try { 2406 final Constructor<? extends Style> ctor = 2407 styleClass.getDeclaredConstructor(); 2408 ctor.setAccessible(true); 2409 final Style style = ctor.newInstance(); 2410 style.restoreFromExtras(mN.extras); 2411 2412 if (style != null) { 2413 setStyle(style); 2414 } 2415 } catch (Throwable t) { 2416 Log.e(TAG, "Could not create Style", t); 2417 } 2418 } 2419 } 2420 2421 } 2422 } 2423 2424 private NotificationColorUtil getColorUtil() { 2425 if (!mColorUtilInited) { 2426 mColorUtilInited = true; 2427 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) { 2428 mColorUtil = NotificationColorUtil.getInstance(mContext); 2429 } 2430 } 2431 return mColorUtil; 2432 } 2433 2434 /** 2435 * Specifies the channel the notification should be delivered on. 2436 */ 2437 public Builder setChannel(String channelId) { 2438 mN.mChannelId = channelId; 2439 return this; 2440 } 2441 2442 /** 2443 * Add a timestamp pertaining to the notification (usually the time the event occurred). 2444 * 2445 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 2446 * shown anymore by default and must be opted into by using 2447 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 2448 * 2449 * @see Notification#when 2450 */ 2451 public Builder setWhen(long when) { 2452 mN.when = when; 2453 return this; 2454 } 2455 2456 /** 2457 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 2458 * in the content view. 2459 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 2460 * {@code false}. For earlier apps, the default is {@code true}. 2461 */ 2462 public Builder setShowWhen(boolean show) { 2463 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 2464 return this; 2465 } 2466 2467 /** 2468 * Show the {@link Notification#when} field as a stopwatch. 2469 * 2470 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 2471 * automatically updating display of the minutes and seconds since <code>when</code>. 2472 * 2473 * Useful when showing an elapsed time (like an ongoing phone call). 2474 * 2475 * The counter can also be set to count down to <code>when</code> when using 2476 * {@link #setChronometerCountDown(boolean)}. 2477 * 2478 * @see android.widget.Chronometer 2479 * @see Notification#when 2480 * @see #setChronometerCountDown(boolean) 2481 */ 2482 public Builder setUsesChronometer(boolean b) { 2483 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 2484 return this; 2485 } 2486 2487 /** 2488 * Sets the Chronometer to count down instead of counting up. 2489 * 2490 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 2491 * If it isn't set the chronometer will count up. 2492 * 2493 * @see #setUsesChronometer(boolean) 2494 */ 2495 public Builder setChronometerCountDown(boolean countDown) { 2496 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 2497 return this; 2498 } 2499 2500 /** 2501 * Set the small icon resource, which will be used to represent the notification in the 2502 * status bar. 2503 * 2504 2505 * The platform template for the expanded view will draw this icon in the left, unless a 2506 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 2507 * icon will be moved to the right-hand side. 2508 * 2509 2510 * @param icon 2511 * A resource ID in the application's package of the drawable to use. 2512 * @see Notification#icon 2513 */ 2514 public Builder setSmallIcon(@DrawableRes int icon) { 2515 return setSmallIcon(icon != 0 2516 ? Icon.createWithResource(mContext, icon) 2517 : null); 2518 } 2519 2520 /** 2521 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 2522 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 2523 * LevelListDrawable}. 2524 * 2525 * @param icon A resource ID in the application's package of the drawable to use. 2526 * @param level The level to use for the icon. 2527 * 2528 * @see Notification#icon 2529 * @see Notification#iconLevel 2530 */ 2531 public Builder setSmallIcon(@DrawableRes int icon, int level) { 2532 mN.iconLevel = level; 2533 return setSmallIcon(icon); 2534 } 2535 2536 /** 2537 * Set the small icon, which will be used to represent the notification in the 2538 * status bar and content view (unless overriden there by a 2539 * {@link #setLargeIcon(Bitmap) large icon}). 2540 * 2541 * @param icon An Icon object to use. 2542 * @see Notification#icon 2543 */ 2544 public Builder setSmallIcon(Icon icon) { 2545 mN.setSmallIcon(icon); 2546 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 2547 mN.icon = icon.getResId(); 2548 } 2549 return this; 2550 } 2551 2552 /** 2553 * Set the first line of text in the platform notification template. 2554 */ 2555 public Builder setContentTitle(CharSequence title) { 2556 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 2557 return this; 2558 } 2559 2560 /** 2561 * Set the second line of text in the platform notification template. 2562 */ 2563 public Builder setContentText(CharSequence text) { 2564 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 2565 return this; 2566 } 2567 2568 /** 2569 * This provides some additional information that is displayed in the notification. No 2570 * guarantees are given where exactly it is displayed. 2571 * 2572 * <p>This information should only be provided if it provides an essential 2573 * benefit to the understanding of the notification. The more text you provide the 2574 * less readable it becomes. For example, an email client should only provide the account 2575 * name here if more than one email account has been added.</p> 2576 * 2577 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 2578 * notification header area. 2579 * 2580 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 2581 * this will be shown in the third line of text in the platform notification template. 2582 * You should not be using {@link #setProgress(int, int, boolean)} at the 2583 * same time on those versions; they occupy the same place. 2584 * </p> 2585 */ 2586 public Builder setSubText(CharSequence text) { 2587 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 2588 return this; 2589 } 2590 2591 /** 2592 * Set the remote input history. 2593 * 2594 * This should be set to the most recent inputs that have been sent 2595 * through a {@link RemoteInput} of this Notification and cleared once the it is no 2596 * longer relevant (e.g. for chat notifications once the other party has responded). 2597 * 2598 * The most recent input must be stored at the 0 index, the second most recent at the 2599 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 2600 * and how much of each individual input is shown. 2601 * 2602 * <p>Note: The reply text will only be shown on notifications that have least one action 2603 * with a {@code RemoteInput}.</p> 2604 */ 2605 public Builder setRemoteInputHistory(CharSequence[] text) { 2606 if (text == null) { 2607 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 2608 } else { 2609 final int N = Math.min(MAX_REPLY_HISTORY, text.length); 2610 CharSequence[] safe = new CharSequence[N]; 2611 for (int i = 0; i < N; i++) { 2612 safe[i] = safeCharSequence(text[i]); 2613 } 2614 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 2615 } 2616 return this; 2617 } 2618 2619 /** 2620 * Set the large number at the right-hand side of the notification. This is 2621 * equivalent to setContentInfo, although it might show the number in a different 2622 * font size for readability. 2623 * 2624 * @deprecated this number is not shown anywhere anymore 2625 */ 2626 @Deprecated 2627 public Builder setNumber(int number) { 2628 mN.number = number; 2629 return this; 2630 } 2631 2632 /** 2633 * A small piece of additional information pertaining to this notification. 2634 * 2635 * The platform template will draw this on the last line of the notification, at the far 2636 * right (to the right of a smallIcon if it has been placed there). 2637 * 2638 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 2639 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 2640 * field will still show up, but the subtext will take precedence. 2641 */ 2642 @Deprecated 2643 public Builder setContentInfo(CharSequence info) { 2644 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 2645 return this; 2646 } 2647 2648 /** 2649 * Set the progress this notification represents. 2650 * 2651 * The platform template will represent this using a {@link ProgressBar}. 2652 */ 2653 public Builder setProgress(int max, int progress, boolean indeterminate) { 2654 mN.extras.putInt(EXTRA_PROGRESS, progress); 2655 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 2656 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 2657 return this; 2658 } 2659 2660 /** 2661 * Supply a custom RemoteViews to use instead of the platform template. 2662 * 2663 * Use {@link #setCustomContentView(RemoteViews)} instead. 2664 */ 2665 @Deprecated 2666 public Builder setContent(RemoteViews views) { 2667 return setCustomContentView(views); 2668 } 2669 2670 /** 2671 * Supply custom RemoteViews to use instead of the platform template. 2672 * 2673 * This will override the layout that would otherwise be constructed by this Builder 2674 * object. 2675 */ 2676 public Builder setCustomContentView(RemoteViews contentView) { 2677 mN.contentView = contentView; 2678 return this; 2679 } 2680 2681 /** 2682 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 2683 * 2684 * This will override the expanded layout that would otherwise be constructed by this 2685 * Builder object. 2686 */ 2687 public Builder setCustomBigContentView(RemoteViews contentView) { 2688 mN.bigContentView = contentView; 2689 return this; 2690 } 2691 2692 /** 2693 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 2694 * 2695 * This will override the heads-up layout that would otherwise be constructed by this 2696 * Builder object. 2697 */ 2698 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 2699 mN.headsUpContentView = contentView; 2700 return this; 2701 } 2702 2703 /** 2704 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 2705 * 2706 * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 2707 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 2708 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 2709 * to assign PendingIntents to individual views in that custom layout (i.e., to create 2710 * clickable buttons inside the notification view). 2711 * 2712 * @see Notification#contentIntent Notification.contentIntent 2713 */ 2714 public Builder setContentIntent(PendingIntent intent) { 2715 mN.contentIntent = intent; 2716 return this; 2717 } 2718 2719 /** 2720 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 2721 * 2722 * @see Notification#deleteIntent 2723 */ 2724 public Builder setDeleteIntent(PendingIntent intent) { 2725 mN.deleteIntent = intent; 2726 return this; 2727 } 2728 2729 /** 2730 * An intent to launch instead of posting the notification to the status bar. 2731 * Only for use with extremely high-priority notifications demanding the user's 2732 * <strong>immediate</strong> attention, such as an incoming phone call or 2733 * alarm clock that the user has explicitly set to a particular time. 2734 * If this facility is used for something else, please give the user an option 2735 * to turn it off and use a normal notification, as this can be extremely 2736 * disruptive. 2737 * 2738 * <p> 2739 * The system UI may choose to display a heads-up notification, instead of 2740 * launching this intent, while the user is using the device. 2741 * </p> 2742 * 2743 * @param intent The pending intent to launch. 2744 * @param highPriority Passing true will cause this notification to be sent 2745 * even if other notifications are suppressed. 2746 * 2747 * @see Notification#fullScreenIntent 2748 */ 2749 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 2750 mN.fullScreenIntent = intent; 2751 setFlag(FLAG_HIGH_PRIORITY, highPriority); 2752 return this; 2753 } 2754 2755 /** 2756 * Set the "ticker" text which is sent to accessibility services. 2757 * 2758 * @see Notification#tickerText 2759 */ 2760 public Builder setTicker(CharSequence tickerText) { 2761 mN.tickerText = safeCharSequence(tickerText); 2762 return this; 2763 } 2764 2765 /** 2766 * Obsolete version of {@link #setTicker(CharSequence)}. 2767 * 2768 */ 2769 @Deprecated 2770 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 2771 setTicker(tickerText); 2772 // views is ignored 2773 return this; 2774 } 2775 2776 /** 2777 * Add a large icon to the notification content view. 2778 * 2779 * In the platform template, this image will be shown on the left of the notification view 2780 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 2781 * badge atop the large icon). 2782 */ 2783 public Builder setLargeIcon(Bitmap b) { 2784 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 2785 } 2786 2787 /** 2788 * Add a large icon to the notification content view. 2789 * 2790 * In the platform template, this image will be shown on the left of the notification view 2791 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 2792 * badge atop the large icon). 2793 */ 2794 public Builder setLargeIcon(Icon icon) { 2795 mN.mLargeIcon = icon; 2796 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 2797 return this; 2798 } 2799 2800 /** 2801 * Set the sound to play. 2802 * 2803 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 2804 * for notifications. 2805 * 2806 * @see Notification#sound 2807 */ 2808 public Builder setSound(Uri sound) { 2809 mN.sound = sound; 2810 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 2811 return this; 2812 } 2813 2814 /** 2815 * Set the sound to play, along with a specific stream on which to play it. 2816 * 2817 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 2818 * 2819 * @deprecated use {@link #setSound(Uri, AudioAttributes)} instead. 2820 * @see Notification#sound 2821 */ 2822 @Deprecated 2823 public Builder setSound(Uri sound, int streamType) { 2824 mN.sound = sound; 2825 mN.audioStreamType = streamType; 2826 return this; 2827 } 2828 2829 /** 2830 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 2831 * use during playback. 2832 * 2833 * @see Notification#sound 2834 */ 2835 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 2836 mN.sound = sound; 2837 mN.audioAttributes = audioAttributes; 2838 return this; 2839 } 2840 2841 /** 2842 * Set the vibration pattern to use. 2843 * 2844 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 2845 * <code>pattern</code> parameter. 2846 * 2847 * <p> 2848 * A notification that vibrates is more likely to be presented as a heads-up notification. 2849 * </p> 2850 * 2851 * @see Notification#vibrate 2852 */ 2853 public Builder setVibrate(long[] pattern) { 2854 mN.vibrate = pattern; 2855 return this; 2856 } 2857 2858 /** 2859 * Set the desired color for the indicator LED on the device, as well as the 2860 * blink duty cycle (specified in milliseconds). 2861 * 2862 2863 * Not all devices will honor all (or even any) of these values. 2864 * 2865 2866 * @see Notification#ledARGB 2867 * @see Notification#ledOnMS 2868 * @see Notification#ledOffMS 2869 */ 2870 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 2871 mN.ledARGB = argb; 2872 mN.ledOnMS = onMs; 2873 mN.ledOffMS = offMs; 2874 if (onMs != 0 || offMs != 0) { 2875 mN.flags |= FLAG_SHOW_LIGHTS; 2876 } 2877 return this; 2878 } 2879 2880 /** 2881 * Set whether this is an "ongoing" notification. 2882 * 2883 2884 * Ongoing notifications cannot be dismissed by the user, so your application or service 2885 * must take care of canceling them. 2886 * 2887 2888 * They are typically used to indicate a background task that the user is actively engaged 2889 * with (e.g., playing music) or is pending in some way and therefore occupying the device 2890 * (e.g., a file download, sync operation, active network connection). 2891 * 2892 2893 * @see Notification#FLAG_ONGOING_EVENT 2894 * @see Service#setForeground(boolean) 2895 */ 2896 public Builder setOngoing(boolean ongoing) { 2897 setFlag(FLAG_ONGOING_EVENT, ongoing); 2898 return this; 2899 } 2900 2901 /** 2902 * Set this flag if you would only like the sound, vibrate 2903 * and ticker to be played if the notification is not already showing. 2904 * 2905 * @see Notification#FLAG_ONLY_ALERT_ONCE 2906 */ 2907 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 2908 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 2909 return this; 2910 } 2911 2912 /** 2913 * Make this notification automatically dismissed when the user touches it. 2914 * 2915 * @see Notification#FLAG_AUTO_CANCEL 2916 */ 2917 public Builder setAutoCancel(boolean autoCancel) { 2918 setFlag(FLAG_AUTO_CANCEL, autoCancel); 2919 return this; 2920 } 2921 2922 /** 2923 * Set whether or not this notification should not bridge to other devices. 2924 * 2925 * <p>Some notifications can be bridged to other devices for remote display. 2926 * This hint can be set to recommend this notification not be bridged. 2927 */ 2928 public Builder setLocalOnly(boolean localOnly) { 2929 setFlag(FLAG_LOCAL_ONLY, localOnly); 2930 return this; 2931 } 2932 2933 /** 2934 * Set which notification properties will be inherited from system defaults. 2935 * <p> 2936 * The value should be one or more of the following fields combined with 2937 * bitwise-or: 2938 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 2939 * <p> 2940 * For all default values, use {@link #DEFAULT_ALL}. 2941 */ 2942 public Builder setDefaults(int defaults) { 2943 mN.defaults = defaults; 2944 return this; 2945 } 2946 2947 /** 2948 * Set the priority of this notification. 2949 * 2950 * @see Notification#priority 2951 */ 2952 public Builder setPriority(@Priority int pri) { 2953 mN.priority = pri; 2954 return this; 2955 } 2956 2957 /** 2958 * Set the notification category. 2959 * 2960 * @see Notification#category 2961 */ 2962 public Builder setCategory(String category) { 2963 mN.category = category; 2964 return this; 2965 } 2966 2967 /** 2968 * Add a person that is relevant to this notification. 2969 * 2970 * <P> 2971 * Depending on user preferences, this annotation may allow the notification to pass 2972 * through interruption filters, and to appear more prominently in the user interface. 2973 * </P> 2974 * 2975 * <P> 2976 * The person should be specified by the {@code String} representation of a 2977 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 2978 * </P> 2979 * 2980 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 2981 * URIs. The path part of these URIs must exist in the contacts database, in the 2982 * appropriate column, or the reference will be discarded as invalid. Telephone schema 2983 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 2984 * </P> 2985 * 2986 * @param uri A URI for the person. 2987 * @see Notification#EXTRA_PEOPLE 2988 */ 2989 public Builder addPerson(String uri) { 2990 mPersonList.add(uri); 2991 return this; 2992 } 2993 2994 /** 2995 * Set this notification to be part of a group of notifications sharing the same key. 2996 * Grouped notifications may display in a cluster or stack on devices which 2997 * support such rendering. 2998 * 2999 * <p>To make this notification the summary for its group, also call 3000 * {@link #setGroupSummary}. A sort order can be specified for group members by using 3001 * {@link #setSortKey}. 3002 * @param groupKey The group key of the group. 3003 * @return this object for method chaining 3004 */ 3005 public Builder setGroup(String groupKey) { 3006 mN.mGroupKey = groupKey; 3007 return this; 3008 } 3009 3010 /** 3011 * Set this notification to be the group summary for a group of notifications. 3012 * Grouped notifications may display in a cluster or stack on devices which 3013 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 3014 * The group summary may be suppressed if too few notifications are included in the group. 3015 * @param isGroupSummary Whether this notification should be a group summary. 3016 * @return this object for method chaining 3017 */ 3018 public Builder setGroupSummary(boolean isGroupSummary) { 3019 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 3020 return this; 3021 } 3022 3023 /** 3024 * Set a sort key that orders this notification among other notifications from the 3025 * same package. This can be useful if an external sort was already applied and an app 3026 * would like to preserve this. Notifications will be sorted lexicographically using this 3027 * value, although providing different priorities in addition to providing sort key may 3028 * cause this value to be ignored. 3029 * 3030 * <p>This sort key can also be used to order members of a notification group. See 3031 * {@link #setGroup}. 3032 * 3033 * @see String#compareTo(String) 3034 */ 3035 public Builder setSortKey(String sortKey) { 3036 mN.mSortKey = sortKey; 3037 return this; 3038 } 3039 3040 /** 3041 * Merge additional metadata into this notification. 3042 * 3043 * <p>Values within the Bundle will replace existing extras values in this Builder. 3044 * 3045 * @see Notification#extras 3046 */ 3047 public Builder addExtras(Bundle extras) { 3048 if (extras != null) { 3049 mUserExtras.putAll(extras); 3050 } 3051 return this; 3052 } 3053 3054 /** 3055 * Set metadata for this notification. 3056 * 3057 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 3058 * current contents are copied into the Notification each time {@link #build()} is 3059 * called. 3060 * 3061 * <p>Replaces any existing extras values with those from the provided Bundle. 3062 * Use {@link #addExtras} to merge in metadata instead. 3063 * 3064 * @see Notification#extras 3065 */ 3066 public Builder setExtras(Bundle extras) { 3067 if (extras != null) { 3068 mUserExtras = extras; 3069 } 3070 return this; 3071 } 3072 3073 /** 3074 * Get the current metadata Bundle used by this notification Builder. 3075 * 3076 * <p>The returned Bundle is shared with this Builder. 3077 * 3078 * <p>The current contents of this Bundle are copied into the Notification each time 3079 * {@link #build()} is called. 3080 * 3081 * @see Notification#extras 3082 */ 3083 public Bundle getExtras() { 3084 return mUserExtras; 3085 } 3086 3087 private Bundle getAllExtras() { 3088 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 3089 saveExtras.putAll(mN.extras); 3090 return saveExtras; 3091 } 3092 3093 /** 3094 * Add an action to this notification. Actions are typically displayed by 3095 * the system as a button adjacent to the notification content. 3096 * <p> 3097 * Every action must have an icon (32dp square and matching the 3098 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 3099 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 3100 * <p> 3101 * A notification in its expanded form can display up to 3 actions, from left to right in 3102 * the order they were added. Actions will not be displayed when the notification is 3103 * collapsed, however, so be sure that any essential functions may be accessed by the user 3104 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 3105 * 3106 * @param icon Resource ID of a drawable that represents the action. 3107 * @param title Text describing the action. 3108 * @param intent PendingIntent to be fired when the action is invoked. 3109 * 3110 * @deprecated Use {@link #addAction(Action)} instead. 3111 */ 3112 @Deprecated 3113 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 3114 mActions.add(new Action(icon, safeCharSequence(title), intent)); 3115 return this; 3116 } 3117 3118 /** 3119 * Add an action to this notification. Actions are typically displayed by 3120 * the system as a button adjacent to the notification content. 3121 * <p> 3122 * Every action must have an icon (32dp square and matching the 3123 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 3124 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 3125 * <p> 3126 * A notification in its expanded form can display up to 3 actions, from left to right in 3127 * the order they were added. Actions will not be displayed when the notification is 3128 * collapsed, however, so be sure that any essential functions may be accessed by the user 3129 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 3130 * 3131 * @param action The action to add. 3132 */ 3133 public Builder addAction(Action action) { 3134 mActions.add(action); 3135 return this; 3136 } 3137 3138 /** 3139 * Alter the complete list of actions attached to this notification. 3140 * @see #addAction(Action). 3141 * 3142 * @param actions 3143 * @return 3144 */ 3145 public Builder setActions(Action... actions) { 3146 mActions.clear(); 3147 for (int i = 0; i < actions.length; i++) { 3148 mActions.add(actions[i]); 3149 } 3150 return this; 3151 } 3152 3153 /** 3154 * Add a rich notification style to be applied at build time. 3155 * 3156 * @param style Object responsible for modifying the notification style. 3157 */ 3158 public Builder setStyle(Style style) { 3159 if (mStyle != style) { 3160 mStyle = style; 3161 if (mStyle != null) { 3162 mStyle.setBuilder(this); 3163 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 3164 } else { 3165 mN.extras.remove(EXTRA_TEMPLATE); 3166 } 3167 } 3168 return this; 3169 } 3170 3171 /** 3172 * Specify the value of {@link #visibility}. 3173 * 3174 * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default), 3175 * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}. 3176 * 3177 * @return The same Builder. 3178 */ 3179 public Builder setVisibility(int visibility) { 3180 mN.visibility = visibility; 3181 return this; 3182 } 3183 3184 /** 3185 * Supply a replacement Notification whose contents should be shown in insecure contexts 3186 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 3187 * @param n A replacement notification, presumably with some or all info redacted. 3188 * @return The same Builder. 3189 */ 3190 public Builder setPublicVersion(Notification n) { 3191 if (n != null) { 3192 mN.publicVersion = new Notification(); 3193 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 3194 } else { 3195 mN.publicVersion = null; 3196 } 3197 return this; 3198 } 3199 3200 /** 3201 * Apply an extender to this notification builder. Extenders may be used to add 3202 * metadata or change options on this builder. 3203 */ 3204 public Builder extend(Extender extender) { 3205 extender.extend(this); 3206 return this; 3207 } 3208 3209 /** 3210 * @hide 3211 */ 3212 public Builder setFlag(int mask, boolean value) { 3213 if (value) { 3214 mN.flags |= mask; 3215 } else { 3216 mN.flags &= ~mask; 3217 } 3218 return this; 3219 } 3220 3221 /** 3222 * Sets {@link Notification#color}. 3223 * 3224 * @param argb The accent color to use 3225 * 3226 * @return The same Builder. 3227 */ 3228 public Builder setColor(@ColorInt int argb) { 3229 mN.color = argb; 3230 sanitizeColor(); 3231 return this; 3232 } 3233 3234 private Drawable getProfileBadgeDrawable() { 3235 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 3236 // This user can never be a badged profile, 3237 // and also includes USER_ALL system notifications. 3238 return null; 3239 } 3240 // Note: This assumes that the current user can read the profile badge of the 3241 // originating user. 3242 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 3243 new UserHandle(mContext.getUserId()), 0); 3244 } 3245 3246 private Bitmap getProfileBadge() { 3247 Drawable badge = getProfileBadgeDrawable(); 3248 if (badge == null) { 3249 return null; 3250 } 3251 final int size = mContext.getResources().getDimensionPixelSize( 3252 R.dimen.notification_badge_size); 3253 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 3254 Canvas canvas = new Canvas(bitmap); 3255 badge.setBounds(0, 0, size, size); 3256 badge.draw(canvas); 3257 return bitmap; 3258 } 3259 3260 private void bindProfileBadge(RemoteViews contentView) { 3261 Bitmap profileBadge = getProfileBadge(); 3262 3263 if (profileBadge != null) { 3264 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 3265 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 3266 } 3267 } 3268 3269 private void resetStandardTemplate(RemoteViews contentView) { 3270 resetNotificationHeader(contentView); 3271 resetContentMargins(contentView); 3272 contentView.setViewVisibility(R.id.right_icon, View.GONE); 3273 contentView.setViewVisibility(R.id.title, View.GONE); 3274 contentView.setTextViewText(R.id.title, null); 3275 contentView.setViewVisibility(R.id.text, View.GONE); 3276 contentView.setTextViewText(R.id.text, null); 3277 contentView.setViewVisibility(R.id.text_line_1, View.GONE); 3278 contentView.setTextViewText(R.id.text_line_1, null); 3279 contentView.setViewVisibility(R.id.progress, View.GONE); 3280 } 3281 3282 /** 3283 * Resets the notification header to its original state 3284 */ 3285 private void resetNotificationHeader(RemoteViews contentView) { 3286 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 3287 // re-using the drawable when the notification is updated. 3288 contentView.setBoolean(R.id.notification_header, "setExpanded", false); 3289 contentView.setTextViewText(R.id.app_name_text, null); 3290 contentView.setViewVisibility(R.id.chronometer, View.GONE); 3291 contentView.setViewVisibility(R.id.header_text, View.GONE); 3292 contentView.setTextViewText(R.id.header_text, null); 3293 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 3294 contentView.setViewVisibility(R.id.time_divider, View.GONE); 3295 contentView.setViewVisibility(R.id.time, View.GONE); 3296 contentView.setImageViewIcon(R.id.profile_badge, null); 3297 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 3298 } 3299 3300 private void resetContentMargins(RemoteViews contentView) { 3301 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); 3302 contentView.setViewLayoutMarginEndDimen(R.id.text, 0); 3303 } 3304 3305 private RemoteViews applyStandardTemplate(int resId) { 3306 return applyStandardTemplate(resId, true /* hasProgress */); 3307 } 3308 3309 /** 3310 * @param hasProgress whether the progress bar should be shown and set 3311 */ 3312 private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) { 3313 final Bundle ex = mN.extras; 3314 3315 CharSequence title = processLegacyText(ex.getCharSequence(EXTRA_TITLE)); 3316 CharSequence text = processLegacyText(ex.getCharSequence(EXTRA_TEXT)); 3317 return applyStandardTemplate(resId, hasProgress, title, text); 3318 } 3319 3320 /** 3321 * @param hasProgress whether the progress bar should be shown and set 3322 */ 3323 private RemoteViews applyStandardTemplate(int resId, boolean hasProgress, 3324 CharSequence title, CharSequence text) { 3325 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 3326 3327 resetStandardTemplate(contentView); 3328 3329 final Bundle ex = mN.extras; 3330 3331 bindNotificationHeader(contentView); 3332 bindLargeIcon(contentView); 3333 boolean showProgress = handleProgressBar(hasProgress, contentView, ex); 3334 if (title != null) { 3335 contentView.setViewVisibility(R.id.title, View.VISIBLE); 3336 contentView.setTextViewText(R.id.title, title); 3337 contentView.setViewLayoutWidth(R.id.title, showProgress 3338 ? ViewGroup.LayoutParams.WRAP_CONTENT 3339 : ViewGroup.LayoutParams.MATCH_PARENT); 3340 } 3341 if (text != null) { 3342 int textId = showProgress ? com.android.internal.R.id.text_line_1 3343 : com.android.internal.R.id.text; 3344 contentView.setTextViewText(textId, text); 3345 contentView.setViewVisibility(textId, View.VISIBLE); 3346 } 3347 3348 setContentMinHeight(contentView, showProgress || mN.hasLargeIcon()); 3349 3350 return contentView; 3351 } 3352 3353 /** 3354 * @param remoteView the remote view to update the minheight in 3355 * @param hasMinHeight does it have a mimHeight 3356 * @hide 3357 */ 3358 void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) { 3359 int minHeight = 0; 3360 if (hasMinHeight) { 3361 // we need to set the minHeight of the notification 3362 minHeight = mContext.getResources().getDimensionPixelSize( 3363 com.android.internal.R.dimen.notification_min_content_height); 3364 } 3365 remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight); 3366 } 3367 3368 private boolean handleProgressBar(boolean hasProgress, RemoteViews contentView, Bundle ex) { 3369 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 3370 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 3371 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 3372 if (hasProgress && (max != 0 || ind)) { 3373 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 3374 contentView.setProgressBar( 3375 R.id.progress, max, progress, ind); 3376 contentView.setProgressBackgroundTintList( 3377 R.id.progress, ColorStateList.valueOf(mContext.getColor( 3378 R.color.notification_progress_background_color))); 3379 if (mN.color != COLOR_DEFAULT) { 3380 ColorStateList colorStateList = ColorStateList.valueOf(resolveContrastColor()); 3381 contentView.setProgressTintList(R.id.progress, colorStateList); 3382 contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); 3383 } 3384 return true; 3385 } else { 3386 contentView.setViewVisibility(R.id.progress, View.GONE); 3387 return false; 3388 } 3389 } 3390 3391 private void bindLargeIcon(RemoteViews contentView) { 3392 if (mN.mLargeIcon == null && mN.largeIcon != null) { 3393 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 3394 } 3395 if (mN.mLargeIcon != null) { 3396 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 3397 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon); 3398 processLargeLegacyIcon(mN.mLargeIcon, contentView); 3399 int endMargin = R.dimen.notification_content_picture_margin; 3400 contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin); 3401 contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin); 3402 contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin); 3403 } 3404 } 3405 3406 private void bindNotificationHeader(RemoteViews contentView) { 3407 bindSmallIcon(contentView); 3408 bindHeaderAppName(contentView); 3409 bindHeaderText(contentView); 3410 bindHeaderChronometerAndTime(contentView); 3411 bindExpandButton(contentView); 3412 bindProfileBadge(contentView); 3413 } 3414 3415 private void bindExpandButton(RemoteViews contentView) { 3416 contentView.setDrawableParameters(R.id.expand_button, false, -1, resolveContrastColor(), 3417 PorterDuff.Mode.SRC_ATOP, -1); 3418 contentView.setInt(R.id.notification_header, "setOriginalNotificationColor", 3419 resolveContrastColor()); 3420 } 3421 3422 private void bindHeaderChronometerAndTime(RemoteViews contentView) { 3423 if (showsTimeOrChronometer()) { 3424 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 3425 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 3426 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 3427 contentView.setLong(R.id.chronometer, "setBase", 3428 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 3429 contentView.setBoolean(R.id.chronometer, "setStarted", true); 3430 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 3431 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 3432 } else { 3433 contentView.setViewVisibility(R.id.time, View.VISIBLE); 3434 contentView.setLong(R.id.time, "setTime", mN.when); 3435 } 3436 } else { 3437 // We still want a time to be set but gone, such that we can show and hide it 3438 // on demand in case it's a child notification without anything in the header 3439 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 3440 } 3441 } 3442 3443 private void bindHeaderText(RemoteViews contentView) { 3444 CharSequence headerText = mN.extras.getCharSequence(EXTRA_SUB_TEXT); 3445 if (headerText == null && mStyle != null && mStyle.mSummaryTextSet 3446 && mStyle.hasSummaryInHeader()) { 3447 headerText = mStyle.mSummaryText; 3448 } 3449 if (headerText == null 3450 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 3451 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 3452 headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 3453 } 3454 if (headerText != null) { 3455 // TODO: Remove the span entirely to only have the string with propper formating. 3456 contentView.setTextViewText(R.id.header_text, processLegacyText(headerText)); 3457 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 3458 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 3459 } 3460 } 3461 3462 /** 3463 * @hide 3464 */ 3465 public String loadHeaderAppName() { 3466 CharSequence name = null; 3467 final PackageManager pm = mContext.getPackageManager(); 3468 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 3469 // only system packages which lump together a bunch of unrelated stuff 3470 // may substitute a different name to make the purpose of the 3471 // notification more clear. the correct package label should always 3472 // be accessible via SystemUI. 3473 final String pkg = mContext.getPackageName(); 3474 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 3475 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 3476 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 3477 name = subName; 3478 } else { 3479 Log.w(TAG, "warning: pkg " 3480 + pkg + " attempting to substitute app name '" + subName 3481 + "' without holding perm " 3482 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 3483 } 3484 } 3485 if (TextUtils.isEmpty(name)) { 3486 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 3487 } 3488 if (TextUtils.isEmpty(name)) { 3489 // still nothing? 3490 return null; 3491 } 3492 3493 return String.valueOf(name); 3494 } 3495 private void bindHeaderAppName(RemoteViews contentView) { 3496 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 3497 contentView.setTextColor(R.id.app_name_text, resolveContrastColor()); 3498 } 3499 3500 private void bindSmallIcon(RemoteViews contentView) { 3501 if (mN.mSmallIcon == null && mN.icon != 0) { 3502 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 3503 } 3504 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 3505 contentView.setDrawableParameters(R.id.icon, false /* targetBackground */, 3506 -1 /* alpha */, -1 /* colorFilter */, null /* mode */, mN.iconLevel); 3507 processSmallIconColor(mN.mSmallIcon, contentView); 3508 } 3509 3510 /** 3511 * @return true if the built notification will show the time or the chronometer; false 3512 * otherwise 3513 */ 3514 private boolean showsTimeOrChronometer() { 3515 return mN.showsTime() || mN.showsChronometer(); 3516 } 3517 3518 private void resetStandardTemplateWithActions(RemoteViews big) { 3519 // actions_container is only reset when there are no actions to avoid focus issues with 3520 // remote inputs. 3521 big.setViewVisibility(R.id.actions, View.GONE); 3522 big.removeAllViews(R.id.actions); 3523 3524 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 3525 big.setTextViewText(R.id.notification_material_reply_text_1, null); 3526 3527 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 3528 big.setTextViewText(R.id.notification_material_reply_text_2, null); 3529 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 3530 big.setTextViewText(R.id.notification_material_reply_text_3, null); 3531 3532 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); 3533 } 3534 3535 private RemoteViews applyStandardTemplateWithActions(int layoutId) { 3536 final Bundle ex = mN.extras; 3537 3538 CharSequence title = processLegacyText(ex.getCharSequence(EXTRA_TITLE)); 3539 CharSequence text = processLegacyText(ex.getCharSequence(EXTRA_TEXT)); 3540 return applyStandardTemplateWithActions(layoutId, true /* hasProgress */, title, text); 3541 } 3542 3543 private RemoteViews applyStandardTemplateWithActions(int layoutId, boolean hasProgress, 3544 CharSequence title, CharSequence text) { 3545 RemoteViews big = applyStandardTemplate(layoutId, hasProgress, title, text); 3546 3547 resetStandardTemplateWithActions(big); 3548 3549 boolean validRemoteInput = false; 3550 3551 int N = mActions.size(); 3552 boolean emphazisedMode = mN.fullScreenIntent != null; 3553 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); 3554 if (N > 0) { 3555 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 3556 big.setViewVisibility(R.id.actions, View.VISIBLE); 3557 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 3558 R.dimen.notification_action_list_height); 3559 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; 3560 for (int i=0; i<N; i++) { 3561 Action action = mActions.get(i); 3562 validRemoteInput |= hasValidRemoteInput(action); 3563 3564 final RemoteViews button = generateActionButton(action, emphazisedMode, 3565 i % 2 != 0); 3566 big.addView(R.id.actions, button); 3567 } 3568 } else { 3569 big.setViewVisibility(R.id.actions_container, View.GONE); 3570 } 3571 3572 CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY); 3573 if (validRemoteInput && replyText != null 3574 && replyText.length > 0 && !TextUtils.isEmpty(replyText[0])) { 3575 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 3576 big.setTextViewText(R.id.notification_material_reply_text_1, replyText[0]); 3577 3578 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1])) { 3579 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 3580 big.setTextViewText(R.id.notification_material_reply_text_2, replyText[1]); 3581 3582 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2])) { 3583 big.setViewVisibility( 3584 R.id.notification_material_reply_text_3, View.VISIBLE); 3585 big.setTextViewText(R.id.notification_material_reply_text_3, replyText[2]); 3586 } 3587 } 3588 } 3589 3590 return big; 3591 } 3592 3593 private boolean hasValidRemoteInput(Action action) { 3594 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 3595 // Weird actions 3596 return false; 3597 } 3598 3599 RemoteInput[] remoteInputs = action.getRemoteInputs(); 3600 if (remoteInputs == null) { 3601 return false; 3602 } 3603 3604 for (RemoteInput r : remoteInputs) { 3605 CharSequence[] choices = r.getChoices(); 3606 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 3607 return true; 3608 } 3609 } 3610 return false; 3611 } 3612 3613 /** 3614 * Construct a RemoteViews for the final 1U notification layout. In order: 3615 * 1. Custom contentView from the caller 3616 * 2. Style's proposed content view 3617 * 3. Standard template view 3618 */ 3619 public RemoteViews createContentView() { 3620 if (mN.contentView != null && (mStyle == null || !mStyle.displayCustomViewInline())) { 3621 return mN.contentView; 3622 } else if (mStyle != null) { 3623 final RemoteViews styleView = mStyle.makeContentView(); 3624 if (styleView != null) { 3625 return styleView; 3626 } 3627 } 3628 return applyStandardTemplate(getBaseLayoutResource()); 3629 } 3630 3631 /** 3632 * Construct a RemoteViews for the final big notification layout. 3633 */ 3634 public RemoteViews createBigContentView() { 3635 RemoteViews result = null; 3636 if (mN.bigContentView != null 3637 && (mStyle == null || !mStyle.displayCustomViewInline())) { 3638 return mN.bigContentView; 3639 } else if (mStyle != null) { 3640 result = mStyle.makeBigContentView(); 3641 hideLine1Text(result); 3642 } else if (mActions.size() != 0) { 3643 result = applyStandardTemplateWithActions(getBigBaseLayoutResource()); 3644 } 3645 adaptNotificationHeaderForBigContentView(result); 3646 return result; 3647 } 3648 3649 /** 3650 * Construct a RemoteViews for the final notification header only 3651 * 3652 * @hide 3653 */ 3654 public RemoteViews makeNotificationHeader() { 3655 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 3656 R.layout.notification_template_header); 3657 resetNotificationHeader(header); 3658 bindNotificationHeader(header); 3659 return header; 3660 } 3661 3662 private void hideLine1Text(RemoteViews result) { 3663 if (result != null) { 3664 result.setViewVisibility(R.id.text_line_1, View.GONE); 3665 } 3666 } 3667 3668 private void adaptNotificationHeaderForBigContentView(RemoteViews result) { 3669 if (result != null) { 3670 result.setBoolean(R.id.notification_header, "setExpanded", true); 3671 } 3672 } 3673 3674 /** 3675 * Construct a RemoteViews for the final heads-up notification layout. 3676 */ 3677 public RemoteViews createHeadsUpContentView() { 3678 if (mN.headsUpContentView != null 3679 && (mStyle == null || !mStyle.displayCustomViewInline())) { 3680 return mN.headsUpContentView; 3681 } else if (mStyle != null) { 3682 final RemoteViews styleView = mStyle.makeHeadsUpContentView(); 3683 if (styleView != null) { 3684 return styleView; 3685 } 3686 } else if (mActions.size() == 0) { 3687 return null; 3688 } 3689 3690 return applyStandardTemplateWithActions(getBigBaseLayoutResource()); 3691 } 3692 3693 /** 3694 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 3695 * 3696 * @hide 3697 */ 3698 public RemoteViews makePublicContentView() { 3699 if (mN.publicVersion != null) { 3700 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 3701 return builder.createContentView(); 3702 } 3703 Bundle savedBundle = mN.extras; 3704 Style style = mStyle; 3705 mStyle = null; 3706 Icon largeIcon = mN.mLargeIcon; 3707 mN.mLargeIcon = null; 3708 Bitmap largeIconLegacy = mN.largeIcon; 3709 mN.largeIcon = null; 3710 Bundle publicExtras = new Bundle(); 3711 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 3712 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 3713 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 3714 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 3715 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 3716 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 3717 publicExtras.putCharSequence(EXTRA_TITLE, 3718 mContext.getString(R.string.notification_hidden_text)); 3719 mN.extras = publicExtras; 3720 final RemoteViews publicView = applyStandardTemplate(getBaseLayoutResource()); 3721 mN.extras = savedBundle; 3722 mN.mLargeIcon = largeIcon; 3723 mN.largeIcon = largeIconLegacy; 3724 mStyle = style; 3725 return publicView; 3726 } 3727 3728 3729 3730 private RemoteViews generateActionButton(Action action, boolean emphazisedMode, 3731 boolean oddAction) { 3732 final boolean tombstone = (action.actionIntent == null); 3733 RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 3734 emphazisedMode ? getEmphasizedActionLayoutResource() 3735 : tombstone ? getActionTombstoneLayoutResource() 3736 : getActionLayoutResource()); 3737 if (!tombstone) { 3738 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 3739 } 3740 button.setContentDescription(R.id.action0, action.title); 3741 if (action.mRemoteInputs != null) { 3742 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 3743 } 3744 if (emphazisedMode) { 3745 // change the background bgColor 3746 int bgColor = mContext.getColor(oddAction ? R.color.notification_action_list 3747 : R.color.notification_action_list_dark); 3748 button.setDrawableParameters(R.id.button_holder, true, -1, bgColor, 3749 PorterDuff.Mode.SRC_ATOP, -1); 3750 CharSequence title = action.title; 3751 ColorStateList[] outResultColor = null; 3752 if (isLegacy()) { 3753 title = clearColorSpans(title); 3754 } else { 3755 outResultColor = new ColorStateList[1]; 3756 title = ensureColorSpanContrast(title, bgColor, outResultColor); 3757 } 3758 button.setTextViewText(R.id.action0, title); 3759 if (outResultColor != null && outResultColor[0] != null) { 3760 // We need to set the text color as well since changing a text to uppercase 3761 // clears its spans. 3762 button.setTextColor(R.id.action0, outResultColor[0]); 3763 } else if (mN.color != COLOR_DEFAULT) { 3764 button.setTextColor(R.id.action0,resolveContrastColor()); 3765 } 3766 } else { 3767 button.setTextViewText(R.id.action0, processLegacyText(action.title)); 3768 if (mN.color != COLOR_DEFAULT) { 3769 button.setTextColor(R.id.action0, resolveContrastColor()); 3770 } 3771 } 3772 return button; 3773 } 3774 3775 /** 3776 * Clears all color spans of a text 3777 * @param charSequence the input text 3778 * @return the same text but without color spans 3779 */ 3780 private CharSequence clearColorSpans(CharSequence charSequence) { 3781 if (charSequence instanceof Spanned) { 3782 Spanned ss = (Spanned) charSequence; 3783 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 3784 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 3785 for (Object span : spans) { 3786 Object resultSpan = span; 3787 if (resultSpan instanceof CharacterStyle) { 3788 resultSpan = ((CharacterStyle) span).getUnderlying(); 3789 } 3790 if (resultSpan instanceof TextAppearanceSpan) { 3791 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 3792 if (originalSpan.getTextColor() != null) { 3793 resultSpan = new TextAppearanceSpan( 3794 originalSpan.getFamily(), 3795 originalSpan.getTextStyle(), 3796 originalSpan.getTextSize(), 3797 null, 3798 originalSpan.getLinkTextColor()); 3799 } 3800 } else if (resultSpan instanceof ForegroundColorSpan 3801 || (resultSpan instanceof BackgroundColorSpan)) { 3802 continue; 3803 } else { 3804 resultSpan = span; 3805 } 3806 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 3807 ss.getSpanFlags(span)); 3808 } 3809 return builder; 3810 } 3811 return charSequence; 3812 } 3813 3814 /** 3815 * Ensures contrast on color spans against a background color. also returns the color of the 3816 * text if a span was found that spans over the whole text. 3817 * 3818 * @param charSequence the charSequence on which the spans are 3819 * @param background the background color to ensure the contrast against 3820 * @param outResultColor an array in which a color will be returned as the first element if 3821 * there exists a full length color span. 3822 * @return the contrasted charSequence 3823 */ 3824 private CharSequence ensureColorSpanContrast(CharSequence charSequence, int background, 3825 ColorStateList[] outResultColor) { 3826 if (charSequence instanceof Spanned) { 3827 Spanned ss = (Spanned) charSequence; 3828 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 3829 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 3830 for (Object span : spans) { 3831 Object resultSpan = span; 3832 int spanStart = ss.getSpanStart(span); 3833 int spanEnd = ss.getSpanEnd(span); 3834 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 3835 if (resultSpan instanceof CharacterStyle) { 3836 resultSpan = ((CharacterStyle) span).getUnderlying(); 3837 } 3838 if (resultSpan instanceof TextAppearanceSpan) { 3839 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 3840 ColorStateList textColor = originalSpan.getTextColor(); 3841 if (textColor != null) { 3842 int[] colors = textColor.getColors(); 3843 int[] newColors = new int[colors.length]; 3844 for (int i = 0; i < newColors.length; i++) { 3845 newColors[i] = NotificationColorUtil.ensureLargeTextContrast( 3846 colors[i], background); 3847 } 3848 textColor = new ColorStateList(textColor.getStates().clone(), 3849 newColors); 3850 resultSpan = new TextAppearanceSpan( 3851 originalSpan.getFamily(), 3852 originalSpan.getTextStyle(), 3853 originalSpan.getTextSize(), 3854 textColor, 3855 originalSpan.getLinkTextColor()); 3856 if (fullLength) { 3857 outResultColor[0] = new ColorStateList( 3858 textColor.getStates().clone(), newColors); 3859 } 3860 } 3861 } else if (resultSpan instanceof ForegroundColorSpan) { 3862 ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; 3863 int foregroundColor = originalSpan.getForegroundColor(); 3864 foregroundColor = NotificationColorUtil.ensureLargeTextContrast( 3865 foregroundColor, background); 3866 resultSpan = new ForegroundColorSpan(foregroundColor); 3867 if (fullLength) { 3868 outResultColor[0] = ColorStateList.valueOf(foregroundColor); 3869 } 3870 } else { 3871 resultSpan = span; 3872 } 3873 3874 builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span)); 3875 } 3876 return builder; 3877 } 3878 return charSequence; 3879 } 3880 3881 /** 3882 * @return Whether we are currently building a notification from a legacy (an app that 3883 * doesn't create material notifications by itself) app. 3884 */ 3885 private boolean isLegacy() { 3886 return getColorUtil() != null; 3887 } 3888 3889 private CharSequence processLegacyText(CharSequence charSequence) { 3890 if (isLegacy()) { 3891 return getColorUtil().invertCharSequenceColors(charSequence); 3892 } else { 3893 return charSequence; 3894 } 3895 } 3896 3897 /** 3898 * Apply any necessariy colors to the small icon 3899 */ 3900 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView) { 3901 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); 3902 if (colorable) { 3903 contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(), 3904 PorterDuff.Mode.SRC_ATOP, -1); 3905 3906 } 3907 contentView.setInt(R.id.notification_header, "setOriginalIconColor", 3908 colorable ? resolveContrastColor() : NotificationHeaderView.NO_COLOR); 3909 } 3910 3911 /** 3912 * Make the largeIcon dark if it's a fake smallIcon (that is, 3913 * if it's grayscale). 3914 */ 3915 // TODO: also check bounds, transparency, that sort of thing. 3916 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) { 3917 if (largeIcon != null && isLegacy() 3918 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 3919 // resolve color will fall back to the default when legacy 3920 contentView.setDrawableParameters(R.id.icon, false, -1, resolveContrastColor(), 3921 PorterDuff.Mode.SRC_ATOP, -1); 3922 } 3923 } 3924 3925 private void sanitizeColor() { 3926 if (mN.color != COLOR_DEFAULT) { 3927 mN.color |= 0xFF000000; // no alpha for custom colors 3928 } 3929 } 3930 3931 int resolveContrastColor() { 3932 if (mCachedContrastColorIsFor == mN.color && mCachedContrastColor != COLOR_INVALID) { 3933 return mCachedContrastColor; 3934 } 3935 final int contrasted = NotificationColorUtil.resolveContrastColor(mContext, mN.color); 3936 3937 mCachedContrastColorIsFor = mN.color; 3938 return mCachedContrastColor = contrasted; 3939 } 3940 3941 /** 3942 * Apply the unstyled operations and return a new {@link Notification} object. 3943 * @hide 3944 */ 3945 public Notification buildUnstyled() { 3946 if (mActions.size() > 0) { 3947 mN.actions = new Action[mActions.size()]; 3948 mActions.toArray(mN.actions); 3949 } 3950 if (!mPersonList.isEmpty()) { 3951 mN.extras.putStringArray(EXTRA_PEOPLE, 3952 mPersonList.toArray(new String[mPersonList.size()])); 3953 } 3954 if (mN.bigContentView != null || mN.contentView != null 3955 || mN.headsUpContentView != null) { 3956 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 3957 } 3958 return mN; 3959 } 3960 3961 /** 3962 * Creates a Builder from an existing notification so further changes can be made. 3963 * @param context The context for your application / activity. 3964 * @param n The notification to create a Builder from. 3965 */ 3966 public static Notification.Builder recoverBuilder(Context context, Notification n) { 3967 // Re-create notification context so we can access app resources. 3968 ApplicationInfo applicationInfo = n.extras.getParcelable( 3969 EXTRA_BUILDER_APPLICATION_INFO); 3970 Context builderContext; 3971 if (applicationInfo != null) { 3972 try { 3973 builderContext = context.createApplicationContext(applicationInfo, 3974 Context.CONTEXT_RESTRICTED); 3975 } catch (NameNotFoundException e) { 3976 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 3977 builderContext = context; // try with our context 3978 } 3979 } else { 3980 builderContext = context; // try with given context 3981 } 3982 3983 return new Builder(builderContext, n); 3984 } 3985 3986 private static Class<? extends Style> getNotificationStyleClass(String templateClass) { 3987 Class<? extends Style>[] classes = new Class[] { 3988 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 3989 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 3990 MessagingStyle.class }; 3991 for (Class<? extends Style> innerClass : classes) { 3992 if (templateClass.equals(innerClass.getName())) { 3993 return innerClass; 3994 } 3995 } 3996 return null; 3997 } 3998 3999 /** 4000 * @deprecated Use {@link #build()} instead. 4001 */ 4002 @Deprecated 4003 public Notification getNotification() { 4004 return build(); 4005 } 4006 4007 /** 4008 * Combine all of the options that have been set and return a new {@link Notification} 4009 * object. 4010 */ 4011 public Notification build() { 4012 // first, add any extras from the calling code 4013 if (mUserExtras != null) { 4014 mN.extras = getAllExtras(); 4015 } 4016 4017 mN.creationTime = System.currentTimeMillis(); 4018 4019 // lazy stuff from mContext; see comment in Builder(Context, Notification) 4020 Notification.addFieldsFromContext(mContext, mN); 4021 4022 buildUnstyled(); 4023 4024 if (mStyle != null) { 4025 mStyle.buildStyled(mN); 4026 } 4027 4028 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 4029 && (mStyle == null || !mStyle.displayCustomViewInline())) { 4030 if (mN.contentView == null) { 4031 mN.contentView = createContentView(); 4032 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 4033 mN.contentView.getSequenceNumber()); 4034 } 4035 if (mN.bigContentView == null) { 4036 mN.bigContentView = createBigContentView(); 4037 if (mN.bigContentView != null) { 4038 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 4039 mN.bigContentView.getSequenceNumber()); 4040 } 4041 } 4042 if (mN.headsUpContentView == null) { 4043 mN.headsUpContentView = createHeadsUpContentView(); 4044 if (mN.headsUpContentView != null) { 4045 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 4046 mN.headsUpContentView.getSequenceNumber()); 4047 } 4048 } 4049 } 4050 4051 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 4052 mN.flags |= FLAG_SHOW_LIGHTS; 4053 } 4054 4055 return mN; 4056 } 4057 4058 /** 4059 * Apply this Builder to an existing {@link Notification} object. 4060 * 4061 * @hide 4062 */ 4063 public Notification buildInto(Notification n) { 4064 build().cloneInto(n, true); 4065 return n; 4066 } 4067 4068 /** 4069 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 4070 * change. 4071 * 4072 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 4073 * 4074 * @hide 4075 */ 4076 public static Notification maybeCloneStrippedForDelivery(Notification n) { 4077 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 4078 4079 // Only strip views for known Styles because we won't know how to 4080 // re-create them otherwise. 4081 if (!TextUtils.isEmpty(templateClass) 4082 && getNotificationStyleClass(templateClass) == null) { 4083 return n; 4084 } 4085 4086 // Only strip unmodified BuilderRemoteViews. 4087 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 4088 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 4089 n.contentView.getSequenceNumber(); 4090 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 4091 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 4092 n.bigContentView.getSequenceNumber(); 4093 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 4094 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 4095 n.headsUpContentView.getSequenceNumber(); 4096 4097 // Nothing to do here, no need to clone. 4098 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 4099 return n; 4100 } 4101 4102 Notification clone = n.clone(); 4103 if (stripContentView) { 4104 clone.contentView = null; 4105 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 4106 } 4107 if (stripBigContentView) { 4108 clone.bigContentView = null; 4109 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 4110 } 4111 if (stripHeadsUpContentView) { 4112 clone.headsUpContentView = null; 4113 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 4114 } 4115 return clone; 4116 } 4117 4118 private int getBaseLayoutResource() { 4119 return R.layout.notification_template_material_base; 4120 } 4121 4122 private int getBigBaseLayoutResource() { 4123 return R.layout.notification_template_material_big_base; 4124 } 4125 4126 private int getBigPictureLayoutResource() { 4127 return R.layout.notification_template_material_big_picture; 4128 } 4129 4130 private int getBigTextLayoutResource() { 4131 return R.layout.notification_template_material_big_text; 4132 } 4133 4134 private int getInboxLayoutResource() { 4135 return R.layout.notification_template_material_inbox; 4136 } 4137 4138 private int getMessagingLayoutResource() { 4139 return R.layout.notification_template_material_messaging; 4140 } 4141 4142 private int getActionLayoutResource() { 4143 return R.layout.notification_material_action; 4144 } 4145 4146 private int getEmphasizedActionLayoutResource() { 4147 return R.layout.notification_material_action_emphasized; 4148 } 4149 4150 private int getActionTombstoneLayoutResource() { 4151 return R.layout.notification_material_action_tombstone; 4152 } 4153 } 4154 4155 private boolean hasLargeIcon() { 4156 return mLargeIcon != null || largeIcon != null; 4157 } 4158 4159 /** 4160 * @return true if the notification will show the time; false otherwise 4161 * @hide 4162 */ 4163 public boolean showsTime() { 4164 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 4165 } 4166 4167 /** 4168 * @return true if the notification will show a chronometer; false otherwise 4169 * @hide 4170 */ 4171 public boolean showsChronometer() { 4172 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 4173 } 4174 4175 /** 4176 * An object that can apply a rich notification style to a {@link Notification.Builder} 4177 * object. 4178 */ 4179 public static abstract class Style { 4180 private CharSequence mBigContentTitle; 4181 4182 /** 4183 * @hide 4184 */ 4185 protected CharSequence mSummaryText = null; 4186 4187 /** 4188 * @hide 4189 */ 4190 protected boolean mSummaryTextSet = false; 4191 4192 protected Builder mBuilder; 4193 4194 /** 4195 * Overrides ContentTitle in the big form of the template. 4196 * This defaults to the value passed to setContentTitle(). 4197 */ 4198 protected void internalSetBigContentTitle(CharSequence title) { 4199 mBigContentTitle = title; 4200 } 4201 4202 /** 4203 * Set the first line of text after the detail section in the big form of the template. 4204 */ 4205 protected void internalSetSummaryText(CharSequence cs) { 4206 mSummaryText = cs; 4207 mSummaryTextSet = true; 4208 } 4209 4210 public void setBuilder(Builder builder) { 4211 if (mBuilder != builder) { 4212 mBuilder = builder; 4213 if (mBuilder != null) { 4214 mBuilder.setStyle(this); 4215 } 4216 } 4217 } 4218 4219 protected void checkBuilder() { 4220 if (mBuilder == null) { 4221 throw new IllegalArgumentException("Style requires a valid Builder object"); 4222 } 4223 } 4224 4225 protected RemoteViews getStandardView(int layoutId) { 4226 checkBuilder(); 4227 4228 // Nasty. 4229 CharSequence oldBuilderContentTitle = 4230 mBuilder.getAllExtras().getCharSequence(EXTRA_TITLE); 4231 if (mBigContentTitle != null) { 4232 mBuilder.setContentTitle(mBigContentTitle); 4233 } 4234 4235 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId); 4236 4237 mBuilder.getAllExtras().putCharSequence(EXTRA_TITLE, oldBuilderContentTitle); 4238 4239 if (mBigContentTitle != null && mBigContentTitle.equals("")) { 4240 contentView.setViewVisibility(R.id.line1, View.GONE); 4241 } else { 4242 contentView.setViewVisibility(R.id.line1, View.VISIBLE); 4243 } 4244 4245 return contentView; 4246 } 4247 4248 /** 4249 * Construct a Style-specific RemoteViews for the final 1U notification layout. 4250 * The default implementation has nothing additional to add. 4251 * @hide 4252 */ 4253 public RemoteViews makeContentView() { 4254 return null; 4255 } 4256 4257 /** 4258 * Construct a Style-specific RemoteViews for the final big notification layout. 4259 * @hide 4260 */ 4261 public RemoteViews makeBigContentView() { 4262 return null; 4263 } 4264 4265 /** 4266 * Construct a Style-specific RemoteViews for the final HUN layout. 4267 * @hide 4268 */ 4269 public RemoteViews makeHeadsUpContentView() { 4270 return null; 4271 } 4272 4273 /** 4274 * Apply any style-specific extras to this notification before shipping it out. 4275 * @hide 4276 */ 4277 public void addExtras(Bundle extras) { 4278 if (mSummaryTextSet) { 4279 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 4280 } 4281 if (mBigContentTitle != null) { 4282 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 4283 } 4284 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 4285 } 4286 4287 /** 4288 * Reconstruct the internal state of this Style object from extras. 4289 * @hide 4290 */ 4291 protected void restoreFromExtras(Bundle extras) { 4292 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 4293 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 4294 mSummaryTextSet = true; 4295 } 4296 if (extras.containsKey(EXTRA_TITLE_BIG)) { 4297 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 4298 } 4299 } 4300 4301 4302 /** 4303 * @hide 4304 */ 4305 public Notification buildStyled(Notification wip) { 4306 addExtras(wip.extras); 4307 return wip; 4308 } 4309 4310 /** 4311 * @hide 4312 */ 4313 public void purgeResources() {} 4314 4315 /** 4316 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 4317 * attached to. 4318 * 4319 * @return the fully constructed Notification. 4320 */ 4321 public Notification build() { 4322 checkBuilder(); 4323 return mBuilder.build(); 4324 } 4325 4326 /** 4327 * @hide 4328 * @return true if the style positions the progress bar on the second line; false if the 4329 * style hides the progress bar 4330 */ 4331 protected boolean hasProgress() { 4332 return true; 4333 } 4334 4335 /** 4336 * @hide 4337 * @return Whether we should put the summary be put into the notification header 4338 */ 4339 public boolean hasSummaryInHeader() { 4340 return true; 4341 } 4342 4343 /** 4344 * @hide 4345 * @return Whether custom content views are displayed inline in the style 4346 */ 4347 public boolean displayCustomViewInline() { 4348 return false; 4349 } 4350 } 4351 4352 /** 4353 * Helper class for generating large-format notifications that include a large image attachment. 4354 * 4355 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 4356 * <pre class="prettyprint"> 4357 * Notification notif = new Notification.Builder(mContext) 4358 * .setContentTitle("New photo from " + sender.toString()) 4359 * .setContentText(subject) 4360 * .setSmallIcon(R.drawable.new_post) 4361 * .setLargeIcon(aBitmap) 4362 * .setStyle(new Notification.BigPictureStyle() 4363 * .bigPicture(aBigBitmap)) 4364 * .build(); 4365 * </pre> 4366 * 4367 * @see Notification#bigContentView 4368 */ 4369 public static class BigPictureStyle extends Style { 4370 private Bitmap mPicture; 4371 private Icon mBigLargeIcon; 4372 private boolean mBigLargeIconSet = false; 4373 4374 public BigPictureStyle() { 4375 } 4376 4377 /** 4378 * @deprecated use {@code BigPictureStyle()}. 4379 */ 4380 @Deprecated 4381 public BigPictureStyle(Builder builder) { 4382 setBuilder(builder); 4383 } 4384 4385 /** 4386 * Overrides ContentTitle in the big form of the template. 4387 * This defaults to the value passed to setContentTitle(). 4388 */ 4389 public BigPictureStyle setBigContentTitle(CharSequence title) { 4390 internalSetBigContentTitle(safeCharSequence(title)); 4391 return this; 4392 } 4393 4394 /** 4395 * Set the first line of text after the detail section in the big form of the template. 4396 */ 4397 public BigPictureStyle setSummaryText(CharSequence cs) { 4398 internalSetSummaryText(safeCharSequence(cs)); 4399 return this; 4400 } 4401 4402 /** 4403 * Provide the bitmap to be used as the payload for the BigPicture notification. 4404 */ 4405 public BigPictureStyle bigPicture(Bitmap b) { 4406 mPicture = b; 4407 return this; 4408 } 4409 4410 /** 4411 * Override the large icon when the big notification is shown. 4412 */ 4413 public BigPictureStyle bigLargeIcon(Bitmap b) { 4414 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4415 } 4416 4417 /** 4418 * Override the large icon when the big notification is shown. 4419 */ 4420 public BigPictureStyle bigLargeIcon(Icon icon) { 4421 mBigLargeIconSet = true; 4422 mBigLargeIcon = icon; 4423 return this; 4424 } 4425 4426 /** @hide */ 4427 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 4428 4429 /** 4430 * @hide 4431 */ 4432 @Override 4433 public void purgeResources() { 4434 super.purgeResources(); 4435 if (mPicture != null && 4436 mPicture.isMutable() && 4437 mPicture.getAllocationByteCount() >= MIN_ASHMEM_BITMAP_SIZE) { 4438 mPicture = mPicture.createAshmemBitmap(); 4439 } 4440 if (mBigLargeIcon != null) { 4441 mBigLargeIcon.convertToAshmem(); 4442 } 4443 } 4444 4445 /** 4446 * @hide 4447 */ 4448 public RemoteViews makeBigContentView() { 4449 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 4450 // This covers the following cases: 4451 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 4452 // mN.mLargeIcon 4453 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 4454 Icon oldLargeIcon = null; 4455 Bitmap largeIconLegacy = null; 4456 if (mBigLargeIconSet) { 4457 oldLargeIcon = mBuilder.mN.mLargeIcon; 4458 mBuilder.mN.mLargeIcon = mBigLargeIcon; 4459 // The legacy largeIcon might not allow us to clear the image, as it's taken in 4460 // replacement if the other one is null. Because we're restoring these legacy icons 4461 // for old listeners, this is in general non-null. 4462 largeIconLegacy = mBuilder.mN.largeIcon; 4463 mBuilder.mN.largeIcon = null; 4464 } 4465 4466 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource()); 4467 if (mSummaryTextSet) { 4468 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(mSummaryText)); 4469 contentView.setViewVisibility(R.id.text, View.VISIBLE); 4470 } 4471 mBuilder.setContentMinHeight(contentView, mBuilder.mN.hasLargeIcon()); 4472 4473 if (mBigLargeIconSet) { 4474 mBuilder.mN.mLargeIcon = oldLargeIcon; 4475 mBuilder.mN.largeIcon = largeIconLegacy; 4476 } 4477 4478 contentView.setImageViewBitmap(R.id.big_picture, mPicture); 4479 return contentView; 4480 } 4481 4482 /** 4483 * @hide 4484 */ 4485 public void addExtras(Bundle extras) { 4486 super.addExtras(extras); 4487 4488 if (mBigLargeIconSet) { 4489 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 4490 } 4491 extras.putParcelable(EXTRA_PICTURE, mPicture); 4492 } 4493 4494 /** 4495 * @hide 4496 */ 4497 @Override 4498 protected void restoreFromExtras(Bundle extras) { 4499 super.restoreFromExtras(extras); 4500 4501 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 4502 mBigLargeIconSet = true; 4503 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG); 4504 } 4505 mPicture = extras.getParcelable(EXTRA_PICTURE); 4506 } 4507 4508 /** 4509 * @hide 4510 */ 4511 @Override 4512 public boolean hasSummaryInHeader() { 4513 return false; 4514 } 4515 } 4516 4517 /** 4518 * Helper class for generating large-format notifications that include a lot of text. 4519 * 4520 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 4521 * <pre class="prettyprint"> 4522 * Notification notif = new Notification.Builder(mContext) 4523 * .setContentTitle("New mail from " + sender.toString()) 4524 * .setContentText(subject) 4525 * .setSmallIcon(R.drawable.new_mail) 4526 * .setLargeIcon(aBitmap) 4527 * .setStyle(new Notification.BigTextStyle() 4528 * .bigText(aVeryLongString)) 4529 * .build(); 4530 * </pre> 4531 * 4532 * @see Notification#bigContentView 4533 */ 4534 public static class BigTextStyle extends Style { 4535 4536 private static final int MAX_LINES = 13; 4537 private static final int LINES_CONSUMED_BY_ACTIONS = 4; 4538 4539 private CharSequence mBigText; 4540 4541 public BigTextStyle() { 4542 } 4543 4544 /** 4545 * @deprecated use {@code BigTextStyle()}. 4546 */ 4547 @Deprecated 4548 public BigTextStyle(Builder builder) { 4549 setBuilder(builder); 4550 } 4551 4552 /** 4553 * Overrides ContentTitle in the big form of the template. 4554 * This defaults to the value passed to setContentTitle(). 4555 */ 4556 public BigTextStyle setBigContentTitle(CharSequence title) { 4557 internalSetBigContentTitle(safeCharSequence(title)); 4558 return this; 4559 } 4560 4561 /** 4562 * Set the first line of text after the detail section in the big form of the template. 4563 */ 4564 public BigTextStyle setSummaryText(CharSequence cs) { 4565 internalSetSummaryText(safeCharSequence(cs)); 4566 return this; 4567 } 4568 4569 /** 4570 * Provide the longer text to be displayed in the big form of the 4571 * template in place of the content text. 4572 */ 4573 public BigTextStyle bigText(CharSequence cs) { 4574 mBigText = safeCharSequence(cs); 4575 return this; 4576 } 4577 4578 /** 4579 * @hide 4580 */ 4581 public void addExtras(Bundle extras) { 4582 super.addExtras(extras); 4583 4584 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 4585 } 4586 4587 /** 4588 * @hide 4589 */ 4590 @Override 4591 protected void restoreFromExtras(Bundle extras) { 4592 super.restoreFromExtras(extras); 4593 4594 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 4595 } 4596 4597 /** 4598 * @hide 4599 */ 4600 public RemoteViews makeBigContentView() { 4601 4602 // Nasty 4603 CharSequence text = mBuilder.getAllExtras().getCharSequence(EXTRA_TEXT); 4604 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null); 4605 4606 RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource()); 4607 4608 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, text); 4609 4610 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 4611 if (TextUtils.isEmpty(bigTextText)) { 4612 // In case the bigtext is null / empty fall back to the normal text to avoid a weird 4613 // experience 4614 bigTextText = mBuilder.processLegacyText(text); 4615 } 4616 applyBigTextContentView(mBuilder, contentView, bigTextText); 4617 4618 return contentView; 4619 } 4620 4621 static void applyBigTextContentView(Builder builder, 4622 RemoteViews contentView, CharSequence bigTextText) { 4623 contentView.setTextViewText(R.id.big_text, bigTextText); 4624 contentView.setViewVisibility(R.id.big_text, 4625 TextUtils.isEmpty(bigTextText) ? View.GONE : View.VISIBLE); 4626 contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines(builder)); 4627 contentView.setBoolean(R.id.big_text, "setHasImage", builder.mN.hasLargeIcon()); 4628 } 4629 4630 private static int calculateMaxLines(Builder builder) { 4631 int lineCount = MAX_LINES; 4632 boolean hasActions = builder.mActions.size() > 0; 4633 if (hasActions) { 4634 lineCount -= LINES_CONSUMED_BY_ACTIONS; 4635 } 4636 return lineCount; 4637 } 4638 } 4639 4640 /** 4641 * Helper class for generating large-format notifications that include multiple back-and-forth 4642 * messages of varying types between any number of people. 4643 * 4644 * <br> 4645 * If the platform does not provide large-format notifications, this method has no effect. The 4646 * user will always see the normal notification view. 4647 * <br> 4648 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like 4649 * so: 4650 * <pre class="prettyprint"> 4651 * 4652 * Notification noti = new Notification.Builder() 4653 * .setContentTitle("2 new messages wtih " + sender.toString()) 4654 * .setContentText(subject) 4655 * .setSmallIcon(R.drawable.new_message) 4656 * .setLargeIcon(aBitmap) 4657 * .setStyle(new Notification.MessagingStyle(resources.getString(R.string.reply_name)) 4658 * .addMessage(messages[0].getText(), messages[0].getTime(), messages[0].getSender()) 4659 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getSender())) 4660 * .build(); 4661 * </pre> 4662 */ 4663 public static class MessagingStyle extends Style { 4664 4665 /** 4666 * The maximum number of messages that will be retained in the Notification itself (the 4667 * number displayed is up to the platform). 4668 */ 4669 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 4670 4671 CharSequence mUserDisplayName; 4672 CharSequence mConversationTitle; 4673 List<Message> mMessages = new ArrayList<>(); 4674 4675 MessagingStyle() { 4676 } 4677 4678 /** 4679 * @param userDisplayName Required - the name to be displayed for any replies sent by the 4680 * user before the posting app reposts the notification with those messages after they've 4681 * been actually sent and in previous messages sent by the user added in 4682 * {@link #addMessage(Notification.MessagingStyle.Message)} 4683 */ 4684 public MessagingStyle(@NonNull CharSequence userDisplayName) { 4685 mUserDisplayName = userDisplayName; 4686 } 4687 4688 /** 4689 * Returns the name to be displayed for any replies sent by the user 4690 */ 4691 public CharSequence getUserDisplayName() { 4692 return mUserDisplayName; 4693 } 4694 4695 /** 4696 * Sets the title to be displayed on this conversation. This should only be used for 4697 * group messaging and left unset for one-on-one conversations. 4698 * @param conversationTitle 4699 * @return this object for method chaining. 4700 */ 4701 public MessagingStyle setConversationTitle(CharSequence conversationTitle) { 4702 mConversationTitle = conversationTitle; 4703 return this; 4704 } 4705 4706 /** 4707 * Return the title to be displayed on this conversation. Can be <code>null</code> and 4708 * should be for one-on-one conversations 4709 */ 4710 public CharSequence getConversationTitle() { 4711 return mConversationTitle; 4712 } 4713 4714 /** 4715 * Adds a message for display by this notification. Convenience call for a simple 4716 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 4717 * @param text A {@link CharSequence} to be displayed as the message content 4718 * @param timestamp Time at which the message arrived 4719 * @param sender A {@link CharSequence} to be used for displaying the name of the 4720 * sender. Should be <code>null</code> for messages by the current user, in which case 4721 * the platform will insert {@link #getUserDisplayName()}. 4722 * Should be unique amongst all individuals in the conversation, and should be 4723 * consistent during re-posts of the notification. 4724 * 4725 * @see Message#Message(CharSequence, long, CharSequence) 4726 * 4727 * @return this object for method chaining 4728 */ 4729 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 4730 mMessages.add(new Message(text, timestamp, sender)); 4731 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 4732 mMessages.remove(0); 4733 } 4734 return this; 4735 } 4736 4737 /** 4738 * Adds a {@link Message} for display in this notification. 4739 * @param message The {@link Message} to be displayed 4740 * @return this object for method chaining 4741 */ 4742 public MessagingStyle addMessage(Message message) { 4743 mMessages.add(message); 4744 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 4745 mMessages.remove(0); 4746 } 4747 return this; 4748 } 4749 4750 /** 4751 * Gets the list of {@code Message} objects that represent the notification 4752 */ 4753 public List<Message> getMessages() { 4754 return mMessages; 4755 } 4756 4757 /** 4758 * @hide 4759 */ 4760 @Override 4761 public void addExtras(Bundle extras) { 4762 super.addExtras(extras); 4763 if (mUserDisplayName != null) { 4764 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUserDisplayName); 4765 } 4766 if (mConversationTitle != null) { 4767 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 4768 } 4769 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES, 4770 Message.getBundleArrayForMessages(mMessages)); 4771 } 4772 4773 fixTitleAndTextExtras(extras); 4774 } 4775 4776 private void fixTitleAndTextExtras(Bundle extras) { 4777 Message m = findLatestIncomingMessage(); 4778 CharSequence text = (m == null) ? null : m.mText; 4779 CharSequence sender = m == null ? null 4780 : TextUtils.isEmpty(m.mSender) ? mUserDisplayName : m.mSender; 4781 CharSequence title; 4782 if (!TextUtils.isEmpty(mConversationTitle)) { 4783 if (!TextUtils.isEmpty(sender)) { 4784 BidiFormatter bidi = BidiFormatter.getInstance(); 4785 title = mBuilder.mContext.getString( 4786 com.android.internal.R.string.notification_messaging_title_template, 4787 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(m.mSender)); 4788 } else { 4789 title = mConversationTitle; 4790 } 4791 } else { 4792 title = sender; 4793 } 4794 4795 if (title != null) { 4796 extras.putCharSequence(EXTRA_TITLE, title); 4797 } 4798 if (text != null) { 4799 extras.putCharSequence(EXTRA_TEXT, text); 4800 } 4801 } 4802 4803 /** 4804 * @hide 4805 */ 4806 @Override 4807 protected void restoreFromExtras(Bundle extras) { 4808 super.restoreFromExtras(extras); 4809 4810 mMessages.clear(); 4811 mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 4812 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 4813 Parcelable[] parcelables = extras.getParcelableArray(EXTRA_MESSAGES); 4814 if (parcelables != null && parcelables instanceof Parcelable[]) { 4815 mMessages = Message.getMessagesFromBundleArray(parcelables); 4816 } 4817 } 4818 4819 /** 4820 * @hide 4821 */ 4822 @Override 4823 public RemoteViews makeContentView() { 4824 Message m = findLatestIncomingMessage(); 4825 CharSequence title = mConversationTitle != null 4826 ? mConversationTitle 4827 : (m == null) ? null : m.mSender; 4828 CharSequence text = (m == null) 4829 ? null 4830 : mConversationTitle != null ? makeMessageLine(m) : m.mText; 4831 4832 return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(), 4833 false /* hasProgress */, 4834 title, 4835 text); 4836 } 4837 4838 private Message findLatestIncomingMessage() { 4839 for (int i = mMessages.size() - 1; i >= 0; i--) { 4840 Message m = mMessages.get(i); 4841 // Incoming messages have a non-empty sender. 4842 if (!TextUtils.isEmpty(m.mSender)) { 4843 return m; 4844 } 4845 } 4846 if (!mMessages.isEmpty()) { 4847 // No incoming messages, fall back to outgoing message 4848 return mMessages.get(mMessages.size() - 1); 4849 } 4850 return null; 4851 } 4852 4853 /** 4854 * @hide 4855 */ 4856 @Override 4857 public RemoteViews makeBigContentView() { 4858 CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle) 4859 ? super.mBigContentTitle 4860 : mConversationTitle; 4861 boolean hasTitle = !TextUtils.isEmpty(title); 4862 4863 if (mMessages.size() == 1) { 4864 // Special case for a single message: Use the big text style 4865 // so the collapsed and expanded versions match nicely. 4866 CharSequence bigTitle; 4867 CharSequence text; 4868 if (hasTitle) { 4869 bigTitle = title; 4870 text = makeMessageLine(mMessages.get(0)); 4871 } else { 4872 bigTitle = mMessages.get(0).mSender; 4873 text = mMessages.get(0).mText; 4874 } 4875 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 4876 mBuilder.getBigTextLayoutResource(), 4877 false /* progress */, bigTitle, null /* text */); 4878 BigTextStyle.applyBigTextContentView(mBuilder, contentView, text); 4879 return contentView; 4880 } 4881 4882 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 4883 mBuilder.getMessagingLayoutResource(), 4884 false /* hasProgress */, 4885 title, 4886 null /* text */); 4887 4888 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 4889 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 4890 4891 // Make sure all rows are gone in case we reuse a view. 4892 for (int rowId : rowIds) { 4893 contentView.setViewVisibility(rowId, View.GONE); 4894 } 4895 4896 int i=0; 4897 contentView.setViewLayoutMarginBottomDimen(R.id.line1, 4898 hasTitle ? R.dimen.notification_messaging_spacing : 0); 4899 contentView.setInt(R.id.notification_messaging, "setNumIndentLines", 4900 !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2)); 4901 4902 int contractedChildId = View.NO_ID; 4903 Message contractedMessage = findLatestIncomingMessage(); 4904 int firstMessage = Math.max(0, mMessages.size() - rowIds.length); 4905 while (firstMessage + i < mMessages.size() && i < rowIds.length) { 4906 Message m = mMessages.get(firstMessage + i); 4907 int rowId = rowIds[i]; 4908 4909 contentView.setViewVisibility(rowId, View.VISIBLE); 4910 contentView.setTextViewText(rowId, makeMessageLine(m)); 4911 4912 if (contractedMessage == m) { 4913 contractedChildId = rowId; 4914 } 4915 4916 i++; 4917 } 4918 // Record this here to allow transformation between the contracted and expanded views. 4919 contentView.setInt(R.id.notification_messaging, "setContractedChildId", 4920 contractedChildId); 4921 return contentView; 4922 } 4923 4924 private CharSequence makeMessageLine(Message m) { 4925 BidiFormatter bidi = BidiFormatter.getInstance(); 4926 SpannableStringBuilder sb = new SpannableStringBuilder(); 4927 if (TextUtils.isEmpty(m.mSender)) { 4928 CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName; 4929 sb.append(bidi.unicodeWrap(replyName), 4930 makeFontColorSpan(mBuilder.resolveContrastColor()), 4931 0 /* flags */); 4932 } else { 4933 sb.append(bidi.unicodeWrap(m.mSender), 4934 makeFontColorSpan(Color.BLACK), 4935 0 /* flags */); 4936 } 4937 CharSequence text = m.mText == null ? "" : m.mText; 4938 sb.append(" ").append(bidi.unicodeWrap(text)); 4939 return sb; 4940 } 4941 4942 /** 4943 * @hide 4944 */ 4945 @Override 4946 public RemoteViews makeHeadsUpContentView() { 4947 Message m = findLatestIncomingMessage(); 4948 CharSequence title = mConversationTitle != null 4949 ? mConversationTitle 4950 : (m == null) ? null : m.mSender; 4951 CharSequence text = (m == null) 4952 ? null 4953 : mConversationTitle != null ? makeMessageLine(m) : m.mText; 4954 4955 return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(), 4956 false /* hasProgress */, 4957 title, 4958 text); 4959 } 4960 4961 private static TextAppearanceSpan makeFontColorSpan(int color) { 4962 return new TextAppearanceSpan(null, 0, 0, 4963 ColorStateList.valueOf(color), null); 4964 } 4965 4966 public static final class Message { 4967 4968 static final String KEY_TEXT = "text"; 4969 static final String KEY_TIMESTAMP = "time"; 4970 static final String KEY_SENDER = "sender"; 4971 static final String KEY_DATA_MIME_TYPE = "type"; 4972 static final String KEY_DATA_URI= "uri"; 4973 4974 private final CharSequence mText; 4975 private final long mTimestamp; 4976 private final CharSequence mSender; 4977 4978 private String mDataMimeType; 4979 private Uri mDataUri; 4980 4981 /** 4982 * Constructor 4983 * @param text A {@link CharSequence} to be displayed as the message content 4984 * @param timestamp Time at which the message arrived 4985 * @param sender A {@link CharSequence} to be used for displaying the name of the 4986 * sender. Should be <code>null</code> for messages by the current user, in which case 4987 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 4988 * Should be unique amongst all individuals in the conversation, and should be 4989 * consistent during re-posts of the notification. 4990 */ 4991 public Message(CharSequence text, long timestamp, CharSequence sender){ 4992 mText = text; 4993 mTimestamp = timestamp; 4994 mSender = sender; 4995 } 4996 4997 /** 4998 * Sets a binary blob of data and an associated MIME type for a message. In the case 4999 * where the platform doesn't support the MIME type, the original text provided in the 5000 * constructor will be used. 5001 * @param dataMimeType The MIME type of the content. See 5002 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME 5003 * types on Android and Android Wear. 5004 * @param dataUri The uri containing the content whose type is given by the MIME type. 5005 * <p class="note"> 5006 * <ol> 5007 * <li>Notification Listeners including the System UI need permission to access the 5008 * data the Uri points to. The recommended ways to do this are:</li> 5009 * <li>Store the data in your own ContentProvider, making sure that other apps have 5010 * the correct permission to access your provider. The preferred mechanism for 5011 * providing access is to use per-URI permissions which are temporary and only 5012 * grant access to the receiving application. An easy way to create a 5013 * ContentProvider like this is to use the FileProvider helper class.</li> 5014 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 5015 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 5016 * also store non-media types (see MediaStore.Files for more info). Files can be 5017 * inserted into the MediaStore using scanFile() after which a content:// style 5018 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 5019 * Note that once added to the system MediaStore the content is accessible to any 5020 * app on the device.</li> 5021 * </ol> 5022 * @return this object for method chaining 5023 */ 5024 public Message setData(String dataMimeType, Uri dataUri) { 5025 mDataMimeType = dataMimeType; 5026 mDataUri = dataUri; 5027 return this; 5028 } 5029 5030 /** 5031 * Get the text to be used for this message, or the fallback text if a type and content 5032 * Uri have been set 5033 */ 5034 public CharSequence getText() { 5035 return mText; 5036 } 5037 5038 /** 5039 * Get the time at which this message arrived 5040 */ 5041 public long getTimestamp() { 5042 return mTimestamp; 5043 } 5044 5045 /** 5046 * Get the text used to display the contact's name in the messaging experience 5047 */ 5048 public CharSequence getSender() { 5049 return mSender; 5050 } 5051 5052 /** 5053 * Get the MIME type of the data pointed to by the Uri 5054 */ 5055 public String getDataMimeType() { 5056 return mDataMimeType; 5057 } 5058 5059 /** 5060 * Get the the Uri pointing to the content of the message. Can be null, in which case 5061 * {@see #getText()} is used. 5062 */ 5063 public Uri getDataUri() { 5064 return mDataUri; 5065 } 5066 5067 private Bundle toBundle() { 5068 Bundle bundle = new Bundle(); 5069 if (mText != null) { 5070 bundle.putCharSequence(KEY_TEXT, mText); 5071 } 5072 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 5073 if (mSender != null) { 5074 bundle.putCharSequence(KEY_SENDER, mSender); 5075 } 5076 if (mDataMimeType != null) { 5077 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 5078 } 5079 if (mDataUri != null) { 5080 bundle.putParcelable(KEY_DATA_URI, mDataUri); 5081 } 5082 return bundle; 5083 } 5084 5085 static Bundle[] getBundleArrayForMessages(List<Message> messages) { 5086 Bundle[] bundles = new Bundle[messages.size()]; 5087 final int N = messages.size(); 5088 for (int i = 0; i < N; i++) { 5089 bundles[i] = messages.get(i).toBundle(); 5090 } 5091 return bundles; 5092 } 5093 5094 static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) { 5095 List<Message> messages = new ArrayList<>(bundles.length); 5096 for (int i = 0; i < bundles.length; i++) { 5097 if (bundles[i] instanceof Bundle) { 5098 Message message = getMessageFromBundle((Bundle)bundles[i]); 5099 if (message != null) { 5100 messages.add(message); 5101 } 5102 } 5103 } 5104 return messages; 5105 } 5106 5107 static Message getMessageFromBundle(Bundle bundle) { 5108 try { 5109 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 5110 return null; 5111 } else { 5112 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 5113 bundle.getLong(KEY_TIMESTAMP), bundle.getCharSequence(KEY_SENDER)); 5114 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 5115 bundle.containsKey(KEY_DATA_URI)) { 5116 5117 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 5118 (Uri) bundle.getParcelable(KEY_DATA_URI)); 5119 } 5120 return message; 5121 } 5122 } catch (ClassCastException e) { 5123 return null; 5124 } 5125 } 5126 } 5127 } 5128 5129 /** 5130 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 5131 * 5132 * Here's how you'd set the <code>InboxStyle</code> on a notification: 5133 * <pre class="prettyprint"> 5134 * Notification notif = new Notification.Builder(mContext) 5135 * .setContentTitle("5 New mails from " + sender.toString()) 5136 * .setContentText(subject) 5137 * .setSmallIcon(R.drawable.new_mail) 5138 * .setLargeIcon(aBitmap) 5139 * .setStyle(new Notification.InboxStyle() 5140 * .addLine(str1) 5141 * .addLine(str2) 5142 * .setContentTitle("") 5143 * .setSummaryText("+3 more")) 5144 * .build(); 5145 * </pre> 5146 * 5147 * @see Notification#bigContentView 5148 */ 5149 public static class InboxStyle extends Style { 5150 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 5151 5152 public InboxStyle() { 5153 } 5154 5155 /** 5156 * @deprecated use {@code InboxStyle()}. 5157 */ 5158 @Deprecated 5159 public InboxStyle(Builder builder) { 5160 setBuilder(builder); 5161 } 5162 5163 /** 5164 * Overrides ContentTitle in the big form of the template. 5165 * This defaults to the value passed to setContentTitle(). 5166 */ 5167 public InboxStyle setBigContentTitle(CharSequence title) { 5168 internalSetBigContentTitle(safeCharSequence(title)); 5169 return this; 5170 } 5171 5172 /** 5173 * Set the first line of text after the detail section in the big form of the template. 5174 */ 5175 public InboxStyle setSummaryText(CharSequence cs) { 5176 internalSetSummaryText(safeCharSequence(cs)); 5177 return this; 5178 } 5179 5180 /** 5181 * Append a line to the digest section of the Inbox notification. 5182 */ 5183 public InboxStyle addLine(CharSequence cs) { 5184 mTexts.add(safeCharSequence(cs)); 5185 return this; 5186 } 5187 5188 /** 5189 * @hide 5190 */ 5191 public void addExtras(Bundle extras) { 5192 super.addExtras(extras); 5193 5194 CharSequence[] a = new CharSequence[mTexts.size()]; 5195 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 5196 } 5197 5198 /** 5199 * @hide 5200 */ 5201 @Override 5202 protected void restoreFromExtras(Bundle extras) { 5203 super.restoreFromExtras(extras); 5204 5205 mTexts.clear(); 5206 if (extras.containsKey(EXTRA_TEXT_LINES)) { 5207 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 5208 } 5209 } 5210 5211 /** 5212 * @hide 5213 */ 5214 public RemoteViews makeBigContentView() { 5215 // Remove the content text so it disappears unless you have a summary 5216 // Nasty 5217 CharSequence oldBuilderContentText = mBuilder.mN.extras.getCharSequence(EXTRA_TEXT); 5218 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, null); 5219 5220 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource()); 5221 5222 mBuilder.getAllExtras().putCharSequence(EXTRA_TEXT, oldBuilderContentText); 5223 5224 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 5225 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 5226 5227 // Make sure all rows are gone in case we reuse a view. 5228 for (int rowId : rowIds) { 5229 contentView.setViewVisibility(rowId, View.GONE); 5230 } 5231 5232 int i=0; 5233 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 5234 R.dimen.notification_inbox_item_top_padding); 5235 boolean first = true; 5236 int onlyViewId = 0; 5237 int maxRows = rowIds.length; 5238 if (mBuilder.mActions.size() > 0) { 5239 maxRows--; 5240 } 5241 while (i < mTexts.size() && i < maxRows) { 5242 CharSequence str = mTexts.get(i); 5243 if (!TextUtils.isEmpty(str)) { 5244 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 5245 contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str)); 5246 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 5247 handleInboxImageMargin(contentView, rowIds[i], first); 5248 if (first) { 5249 onlyViewId = rowIds[i]; 5250 } else { 5251 onlyViewId = 0; 5252 } 5253 first = false; 5254 } 5255 i++; 5256 } 5257 if (onlyViewId != 0) { 5258 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 5259 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 5260 R.dimen.notification_text_margin_top); 5261 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 5262 } 5263 5264 return contentView; 5265 } 5266 5267 private void handleInboxImageMargin(RemoteViews contentView, int id, boolean first) { 5268 int endMargin = 0; 5269 if (first) { 5270 final int max = mBuilder.mN.extras.getInt(EXTRA_PROGRESS_MAX, 0); 5271 final boolean ind = mBuilder.mN.extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5272 boolean hasProgress = max != 0 || ind; 5273 if (mBuilder.mN.hasLargeIcon() && !hasProgress) { 5274 endMargin = R.dimen.notification_content_picture_margin; 5275 } 5276 } 5277 contentView.setViewLayoutMarginEndDimen(id, endMargin); 5278 } 5279 } 5280 5281 /** 5282 * Notification style for media playback notifications. 5283 * 5284 * In the expanded form, {@link Notification#bigContentView}, up to 5 5285 * {@link Notification.Action}s specified with 5286 * {@link Notification.Builder#addAction(Action) addAction} will be 5287 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 5288 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 5289 * treated as album artwork. 5290 * 5291 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 5292 * {@link Notification#contentView}; by providing action indices to 5293 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 5294 * in the standard view alongside the usual content. 5295 * 5296 * Notifications created with MediaStyle will have their category set to 5297 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 5298 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 5299 * 5300 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 5301 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 5302 * the System UI can identify this as a notification representing an active media session 5303 * and respond accordingly (by showing album artwork in the lockscreen, for example). 5304 * 5305 * To use this style with your Notification, feed it to 5306 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 5307 * <pre class="prettyprint"> 5308 * Notification noti = new Notification.Builder() 5309 * .setSmallIcon(R.drawable.ic_stat_player) 5310 * .setContentTitle("Track title") 5311 * .setContentText("Artist - Album") 5312 * .setLargeIcon(albumArtBitmap)) 5313 * .setStyle(<b>new Notification.MediaStyle()</b> 5314 * .setMediaSession(mySession)) 5315 * .build(); 5316 * </pre> 5317 * 5318 * @see Notification#bigContentView 5319 */ 5320 public static class MediaStyle extends Style { 5321 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 5322 static final int MAX_MEDIA_BUTTONS = 5; 5323 5324 private int[] mActionsToShowInCompact = null; 5325 private MediaSession.Token mToken; 5326 5327 public MediaStyle() { 5328 } 5329 5330 /** 5331 * @deprecated use {@code MediaStyle()}. 5332 */ 5333 @Deprecated 5334 public MediaStyle(Builder builder) { 5335 setBuilder(builder); 5336 } 5337 5338 /** 5339 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 5340 * notification view. 5341 * 5342 * @param actions the indices of the actions to show in the compact notification view 5343 */ 5344 public MediaStyle setShowActionsInCompactView(int...actions) { 5345 mActionsToShowInCompact = actions; 5346 return this; 5347 } 5348 5349 /** 5350 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 5351 * to provide additional playback information and control to the SystemUI. 5352 */ 5353 public MediaStyle setMediaSession(MediaSession.Token token) { 5354 mToken = token; 5355 return this; 5356 } 5357 5358 /** 5359 * @hide 5360 */ 5361 @Override 5362 public Notification buildStyled(Notification wip) { 5363 super.buildStyled(wip); 5364 if (wip.category == null) { 5365 wip.category = Notification.CATEGORY_TRANSPORT; 5366 } 5367 return wip; 5368 } 5369 5370 /** 5371 * @hide 5372 */ 5373 @Override 5374 public RemoteViews makeContentView() { 5375 return makeMediaContentView(); 5376 } 5377 5378 /** 5379 * @hide 5380 */ 5381 @Override 5382 public RemoteViews makeBigContentView() { 5383 return makeMediaBigContentView(); 5384 } 5385 5386 /** 5387 * @hide 5388 */ 5389 @Override 5390 public RemoteViews makeHeadsUpContentView() { 5391 RemoteViews expanded = makeMediaBigContentView(); 5392 return expanded != null ? expanded : makeMediaContentView(); 5393 } 5394 5395 /** @hide */ 5396 @Override 5397 public void addExtras(Bundle extras) { 5398 super.addExtras(extras); 5399 5400 if (mToken != null) { 5401 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 5402 } 5403 if (mActionsToShowInCompact != null) { 5404 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 5405 } 5406 } 5407 5408 /** 5409 * @hide 5410 */ 5411 @Override 5412 protected void restoreFromExtras(Bundle extras) { 5413 super.restoreFromExtras(extras); 5414 5415 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 5416 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); 5417 } 5418 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 5419 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 5420 } 5421 } 5422 5423 private RemoteViews generateMediaActionButton(Action action, int color) { 5424 final boolean tombstone = (action.actionIntent == null); 5425 RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(), 5426 R.layout.notification_material_media_action); 5427 button.setImageViewIcon(R.id.action0, action.getIcon()); 5428 button.setDrawableParameters(R.id.action0, false, -1, color, PorterDuff.Mode.SRC_ATOP, 5429 -1); 5430 if (!tombstone) { 5431 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 5432 } 5433 button.setContentDescription(R.id.action0, action.title); 5434 return button; 5435 } 5436 5437 private RemoteViews makeMediaContentView() { 5438 RemoteViews view = mBuilder.applyStandardTemplate( 5439 R.layout.notification_template_material_media, false /* hasProgress */); 5440 5441 final int numActions = mBuilder.mActions.size(); 5442 final int N = mActionsToShowInCompact == null 5443 ? 0 5444 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 5445 if (N > 0) { 5446 view.removeAllViews(com.android.internal.R.id.media_actions); 5447 for (int i = 0; i < N; i++) { 5448 if (i >= numActions) { 5449 throw new IllegalArgumentException(String.format( 5450 "setShowActionsInCompactView: action %d out of bounds (max %d)", 5451 i, numActions - 1)); 5452 } 5453 5454 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 5455 final RemoteViews button = generateMediaActionButton(action, 5456 mBuilder.resolveContrastColor()); 5457 view.addView(com.android.internal.R.id.media_actions, button); 5458 } 5459 } 5460 handleImage(view); 5461 // handle the content margin 5462 int endMargin = R.dimen.notification_content_margin_end; 5463 if (mBuilder.mN.hasLargeIcon()) { 5464 endMargin = R.dimen.notification_content_plus_picture_margin_end; 5465 } 5466 view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); 5467 return view; 5468 } 5469 5470 private RemoteViews makeMediaBigContentView() { 5471 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 5472 // Dont add an expanded view if there is no more content to be revealed 5473 int actionsInCompact = mActionsToShowInCompact == null 5474 ? 0 5475 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 5476 if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) { 5477 return null; 5478 } 5479 RemoteViews big = mBuilder.applyStandardTemplate( 5480 R.layout.notification_template_material_big_media, 5481 false); 5482 5483 if (actionCount > 0) { 5484 big.removeAllViews(com.android.internal.R.id.media_actions); 5485 for (int i = 0; i < actionCount; i++) { 5486 final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i), 5487 mBuilder.resolveContrastColor()); 5488 big.addView(com.android.internal.R.id.media_actions, button); 5489 } 5490 } 5491 handleImage(big); 5492 return big; 5493 } 5494 5495 private void handleImage(RemoteViews contentView) { 5496 if (mBuilder.mN.hasLargeIcon()) { 5497 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); 5498 contentView.setViewLayoutMarginEndDimen(R.id.text, 0); 5499 } 5500 } 5501 5502 /** 5503 * @hide 5504 */ 5505 @Override 5506 protected boolean hasProgress() { 5507 return false; 5508 } 5509 } 5510 5511 /** 5512 * Notification style for custom views that are decorated by the system 5513 * 5514 * <p>Instead of providing a notification that is completely custom, a developer can set this 5515 * style and still obtain system decorations like the notification header with the expand 5516 * affordance and actions. 5517 * 5518 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 5519 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 5520 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 5521 * corresponding custom views to display. 5522 * 5523 * To use this style with your Notification, feed it to 5524 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 5525 * <pre class="prettyprint"> 5526 * Notification noti = new Notification.Builder() 5527 * .setSmallIcon(R.drawable.ic_stat_player) 5528 * .setLargeIcon(albumArtBitmap)) 5529 * .setCustomContentView(contentView); 5530 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 5531 * .build(); 5532 * </pre> 5533 */ 5534 public static class DecoratedCustomViewStyle extends Style { 5535 5536 public DecoratedCustomViewStyle() { 5537 } 5538 5539 /** 5540 * @hide 5541 */ 5542 public boolean displayCustomViewInline() { 5543 return true; 5544 } 5545 5546 /** 5547 * @hide 5548 */ 5549 @Override 5550 public RemoteViews makeContentView() { 5551 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 5552 } 5553 5554 /** 5555 * @hide 5556 */ 5557 @Override 5558 public RemoteViews makeBigContentView() { 5559 return makeDecoratedBigContentView(); 5560 } 5561 5562 /** 5563 * @hide 5564 */ 5565 @Override 5566 public RemoteViews makeHeadsUpContentView() { 5567 return makeDecoratedHeadsUpContentView(); 5568 } 5569 5570 private RemoteViews makeDecoratedHeadsUpContentView() { 5571 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 5572 ? mBuilder.mN.contentView 5573 : mBuilder.mN.headsUpContentView; 5574 if (mBuilder.mActions.size() == 0) { 5575 return makeStandardTemplateWithCustomContent(headsUpContentView); 5576 } 5577 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 5578 mBuilder.getBigBaseLayoutResource()); 5579 buildIntoRemoteViewContent(remoteViews, headsUpContentView); 5580 return remoteViews; 5581 } 5582 5583 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 5584 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 5585 mBuilder.getBaseLayoutResource()); 5586 buildIntoRemoteViewContent(remoteViews, customContent); 5587 return remoteViews; 5588 } 5589 5590 private RemoteViews makeDecoratedBigContentView() { 5591 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 5592 ? mBuilder.mN.contentView 5593 : mBuilder.mN.bigContentView; 5594 if (mBuilder.mActions.size() == 0) { 5595 return makeStandardTemplateWithCustomContent(bigContentView); 5596 } 5597 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 5598 mBuilder.getBigBaseLayoutResource()); 5599 buildIntoRemoteViewContent(remoteViews, bigContentView); 5600 return remoteViews; 5601 } 5602 5603 private void buildIntoRemoteViewContent(RemoteViews remoteViews, 5604 RemoteViews customContent) { 5605 if (customContent != null) { 5606 // Need to clone customContent before adding, because otherwise it can no longer be 5607 // parceled independently of remoteViews. 5608 customContent = customContent.clone(); 5609 remoteViews.removeAllViews(R.id.notification_main_column); 5610 remoteViews.addView(R.id.notification_main_column, customContent); 5611 } 5612 // also update the end margin if there is an image 5613 int endMargin = R.dimen.notification_content_margin_end; 5614 if (mBuilder.mN.hasLargeIcon()) { 5615 endMargin = R.dimen.notification_content_plus_picture_margin_end; 5616 } 5617 remoteViews.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); 5618 } 5619 } 5620 5621 /** 5622 * Notification style for media custom views that are decorated by the system 5623 * 5624 * <p>Instead of providing a media notification that is completely custom, a developer can set 5625 * this style and still obtain system decorations like the notification header with the expand 5626 * affordance and actions. 5627 * 5628 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 5629 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 5630 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 5631 * corresponding custom views to display. 5632 * 5633 * To use this style with your Notification, feed it to 5634 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 5635 * <pre class="prettyprint"> 5636 * Notification noti = new Notification.Builder() 5637 * .setSmallIcon(R.drawable.ic_stat_player) 5638 * .setLargeIcon(albumArtBitmap)) 5639 * .setCustomContentView(contentView); 5640 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 5641 * .setMediaSession(mySession)) 5642 * .build(); 5643 * </pre> 5644 * 5645 * @see android.app.Notification.DecoratedCustomViewStyle 5646 * @see android.app.Notification.MediaStyle 5647 */ 5648 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 5649 5650 public DecoratedMediaCustomViewStyle() { 5651 } 5652 5653 /** 5654 * @hide 5655 */ 5656 public boolean displayCustomViewInline() { 5657 return true; 5658 } 5659 5660 /** 5661 * @hide 5662 */ 5663 @Override 5664 public RemoteViews makeContentView() { 5665 RemoteViews remoteViews = super.makeContentView(); 5666 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 5667 mBuilder.mN.contentView); 5668 } 5669 5670 /** 5671 * @hide 5672 */ 5673 @Override 5674 public RemoteViews makeBigContentView() { 5675 RemoteViews customRemoteView = mBuilder.mN.bigContentView != null 5676 ? mBuilder.mN.bigContentView 5677 : mBuilder.mN.contentView; 5678 return makeBigContentViewWithCustomContent(customRemoteView); 5679 } 5680 5681 private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) { 5682 RemoteViews remoteViews = super.makeBigContentView(); 5683 if (remoteViews != null) { 5684 return buildIntoRemoteView(remoteViews, R.id.notification_main_column, 5685 customRemoteView); 5686 } else if (customRemoteView != mBuilder.mN.contentView){ 5687 remoteViews = super.makeContentView(); 5688 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 5689 customRemoteView); 5690 } else { 5691 return null; 5692 } 5693 } 5694 5695 /** 5696 * @hide 5697 */ 5698 @Override 5699 public RemoteViews makeHeadsUpContentView() { 5700 RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null 5701 ? mBuilder.mN.headsUpContentView 5702 : mBuilder.mN.contentView; 5703 return makeBigContentViewWithCustomContent(customRemoteView); 5704 } 5705 5706 private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, 5707 RemoteViews customContent) { 5708 if (customContent != null) { 5709 // Need to clone customContent before adding, because otherwise it can no longer be 5710 // parceled independently of remoteViews. 5711 customContent = customContent.clone(); 5712 remoteViews.removeAllViews(id); 5713 remoteViews.addView(id, customContent); 5714 } 5715 return remoteViews; 5716 } 5717 } 5718 5719 // When adding a new Style subclass here, don't forget to update 5720 // Builder.getNotificationStyleClass. 5721 5722 /** 5723 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 5724 * metadata or change options on a notification builder. 5725 */ 5726 public interface Extender { 5727 /** 5728 * Apply this extender to a notification builder. 5729 * @param builder the builder to be modified. 5730 * @return the build object for chaining. 5731 */ 5732 public Builder extend(Builder builder); 5733 } 5734 5735 /** 5736 * Helper class to add wearable extensions to notifications. 5737 * <p class="note"> See 5738 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 5739 * for Android Wear</a> for more information on how to use this class. 5740 * <p> 5741 * To create a notification with wearable extensions: 5742 * <ol> 5743 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 5744 * properties. 5745 * <li>Create a {@link android.app.Notification.WearableExtender}. 5746 * <li>Set wearable-specific properties using the 5747 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 5748 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 5749 * notification. 5750 * <li>Post the notification to the notification system with the 5751 * {@code NotificationManager.notify(...)} methods. 5752 * </ol> 5753 * 5754 * <pre class="prettyprint"> 5755 * Notification notif = new Notification.Builder(mContext) 5756 * .setContentTitle("New mail from " + sender.toString()) 5757 * .setContentText(subject) 5758 * .setSmallIcon(R.drawable.new_mail) 5759 * .extend(new Notification.WearableExtender() 5760 * .setContentIcon(R.drawable.new_mail)) 5761 * .build(); 5762 * NotificationManager notificationManger = 5763 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 5764 * notificationManger.notify(0, notif);</pre> 5765 * 5766 * <p>Wearable extensions can be accessed on an existing notification by using the 5767 * {@code WearableExtender(Notification)} constructor, 5768 * and then using the {@code get} methods to access values. 5769 * 5770 * <pre class="prettyprint"> 5771 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 5772 * notification); 5773 * List<Notification> pages = wearableExtender.getPages();</pre> 5774 */ 5775 public static final class WearableExtender implements Extender { 5776 /** 5777 * Sentinel value for an action index that is unset. 5778 */ 5779 public static final int UNSET_ACTION_INDEX = -1; 5780 5781 /** 5782 * Size value for use with {@link #setCustomSizePreset} to show this notification with 5783 * default sizing. 5784 * <p>For custom display notifications created using {@link #setDisplayIntent}, 5785 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 5786 * on their content. 5787 */ 5788 public static final int SIZE_DEFAULT = 0; 5789 5790 /** 5791 * Size value for use with {@link #setCustomSizePreset} to show this notification 5792 * with an extra small size. 5793 * <p>This value is only applicable for custom display notifications created using 5794 * {@link #setDisplayIntent}. 5795 */ 5796 public static final int SIZE_XSMALL = 1; 5797 5798 /** 5799 * Size value for use with {@link #setCustomSizePreset} to show this notification 5800 * with a small size. 5801 * <p>This value is only applicable for custom display notifications created using 5802 * {@link #setDisplayIntent}. 5803 */ 5804 public static final int SIZE_SMALL = 2; 5805 5806 /** 5807 * Size value for use with {@link #setCustomSizePreset} to show this notification 5808 * with a medium size. 5809 * <p>This value is only applicable for custom display notifications created using 5810 * {@link #setDisplayIntent}. 5811 */ 5812 public static final int SIZE_MEDIUM = 3; 5813 5814 /** 5815 * Size value for use with {@link #setCustomSizePreset} to show this notification 5816 * with a large size. 5817 * <p>This value is only applicable for custom display notifications created using 5818 * {@link #setDisplayIntent}. 5819 */ 5820 public static final int SIZE_LARGE = 4; 5821 5822 /** 5823 * Size value for use with {@link #setCustomSizePreset} to show this notification 5824 * full screen. 5825 * <p>This value is only applicable for custom display notifications created using 5826 * {@link #setDisplayIntent}. 5827 */ 5828 public static final int SIZE_FULL_SCREEN = 5; 5829 5830 /** 5831 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 5832 * short amount of time when this notification is displayed on the screen. This 5833 * is the default value. 5834 */ 5835 public static final int SCREEN_TIMEOUT_SHORT = 0; 5836 5837 /** 5838 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 5839 * for a longer amount of time when this notification is displayed on the screen. 5840 */ 5841 public static final int SCREEN_TIMEOUT_LONG = -1; 5842 5843 /** Notification extra which contains wearable extensions */ 5844 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 5845 5846 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 5847 private static final String KEY_ACTIONS = "actions"; 5848 private static final String KEY_FLAGS = "flags"; 5849 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 5850 private static final String KEY_PAGES = "pages"; 5851 private static final String KEY_BACKGROUND = "background"; 5852 private static final String KEY_CONTENT_ICON = "contentIcon"; 5853 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 5854 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 5855 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 5856 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 5857 private static final String KEY_GRAVITY = "gravity"; 5858 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 5859 private static final String KEY_DISMISSAL_ID = "dismissalId"; 5860 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 5861 5862 // Flags bitwise-ored to mFlags 5863 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 5864 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 5865 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 5866 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 5867 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 5868 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 5869 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 5870 5871 // Default value for flags integer 5872 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 5873 5874 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 5875 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 5876 5877 private ArrayList<Action> mActions = new ArrayList<Action>(); 5878 private int mFlags = DEFAULT_FLAGS; 5879 private PendingIntent mDisplayIntent; 5880 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 5881 private Bitmap mBackground; 5882 private int mContentIcon; 5883 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 5884 private int mContentActionIndex = UNSET_ACTION_INDEX; 5885 private int mCustomSizePreset = SIZE_DEFAULT; 5886 private int mCustomContentHeight; 5887 private int mGravity = DEFAULT_GRAVITY; 5888 private int mHintScreenTimeout; 5889 private String mDismissalId; 5890 private String mBridgeTag; 5891 5892 /** 5893 * Create a {@link android.app.Notification.WearableExtender} with default 5894 * options. 5895 */ 5896 public WearableExtender() { 5897 } 5898 5899 public WearableExtender(Notification notif) { 5900 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 5901 if (wearableBundle != null) { 5902 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 5903 if (actions != null) { 5904 mActions.addAll(actions); 5905 } 5906 5907 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 5908 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 5909 5910 Notification[] pages = getNotificationArrayFromBundle( 5911 wearableBundle, KEY_PAGES); 5912 if (pages != null) { 5913 Collections.addAll(mPages, pages); 5914 } 5915 5916 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 5917 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 5918 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 5919 DEFAULT_CONTENT_ICON_GRAVITY); 5920 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 5921 UNSET_ACTION_INDEX); 5922 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 5923 SIZE_DEFAULT); 5924 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 5925 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 5926 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 5927 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 5928 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 5929 } 5930 } 5931 5932 /** 5933 * Apply wearable extensions to a notification that is being built. This is typically 5934 * called by the {@link android.app.Notification.Builder#extend} method of 5935 * {@link android.app.Notification.Builder}. 5936 */ 5937 @Override 5938 public Notification.Builder extend(Notification.Builder builder) { 5939 Bundle wearableBundle = new Bundle(); 5940 5941 if (!mActions.isEmpty()) { 5942 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 5943 } 5944 if (mFlags != DEFAULT_FLAGS) { 5945 wearableBundle.putInt(KEY_FLAGS, mFlags); 5946 } 5947 if (mDisplayIntent != null) { 5948 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 5949 } 5950 if (!mPages.isEmpty()) { 5951 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 5952 new Notification[mPages.size()])); 5953 } 5954 if (mBackground != null) { 5955 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 5956 } 5957 if (mContentIcon != 0) { 5958 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 5959 } 5960 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 5961 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 5962 } 5963 if (mContentActionIndex != UNSET_ACTION_INDEX) { 5964 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 5965 mContentActionIndex); 5966 } 5967 if (mCustomSizePreset != SIZE_DEFAULT) { 5968 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 5969 } 5970 if (mCustomContentHeight != 0) { 5971 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 5972 } 5973 if (mGravity != DEFAULT_GRAVITY) { 5974 wearableBundle.putInt(KEY_GRAVITY, mGravity); 5975 } 5976 if (mHintScreenTimeout != 0) { 5977 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 5978 } 5979 if (mDismissalId != null) { 5980 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 5981 } 5982 if (mBridgeTag != null) { 5983 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 5984 } 5985 5986 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 5987 return builder; 5988 } 5989 5990 @Override 5991 public WearableExtender clone() { 5992 WearableExtender that = new WearableExtender(); 5993 that.mActions = new ArrayList<Action>(this.mActions); 5994 that.mFlags = this.mFlags; 5995 that.mDisplayIntent = this.mDisplayIntent; 5996 that.mPages = new ArrayList<Notification>(this.mPages); 5997 that.mBackground = this.mBackground; 5998 that.mContentIcon = this.mContentIcon; 5999 that.mContentIconGravity = this.mContentIconGravity; 6000 that.mContentActionIndex = this.mContentActionIndex; 6001 that.mCustomSizePreset = this.mCustomSizePreset; 6002 that.mCustomContentHeight = this.mCustomContentHeight; 6003 that.mGravity = this.mGravity; 6004 that.mHintScreenTimeout = this.mHintScreenTimeout; 6005 that.mDismissalId = this.mDismissalId; 6006 that.mBridgeTag = this.mBridgeTag; 6007 return that; 6008 } 6009 6010 /** 6011 * Add a wearable action to this notification. 6012 * 6013 * <p>When wearable actions are added using this method, the set of actions that 6014 * show on a wearable device splits from devices that only show actions added 6015 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 6016 * of which actions display on different devices. 6017 * 6018 * @param action the action to add to this notification 6019 * @return this object for method chaining 6020 * @see android.app.Notification.Action 6021 */ 6022 public WearableExtender addAction(Action action) { 6023 mActions.add(action); 6024 return this; 6025 } 6026 6027 /** 6028 * Adds wearable actions to this notification. 6029 * 6030 * <p>When wearable actions are added using this method, the set of actions that 6031 * show on a wearable device splits from devices that only show actions added 6032 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 6033 * of which actions display on different devices. 6034 * 6035 * @param actions the actions to add to this notification 6036 * @return this object for method chaining 6037 * @see android.app.Notification.Action 6038 */ 6039 public WearableExtender addActions(List<Action> actions) { 6040 mActions.addAll(actions); 6041 return this; 6042 } 6043 6044 /** 6045 * Clear all wearable actions present on this builder. 6046 * @return this object for method chaining. 6047 * @see #addAction 6048 */ 6049 public WearableExtender clearActions() { 6050 mActions.clear(); 6051 return this; 6052 } 6053 6054 /** 6055 * Get the wearable actions present on this notification. 6056 */ 6057 public List<Action> getActions() { 6058 return mActions; 6059 } 6060 6061 /** 6062 * Set an intent to launch inside of an activity view when displaying 6063 * this notification. The {@link PendingIntent} provided should be for an activity. 6064 * 6065 * <pre class="prettyprint"> 6066 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 6067 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 6068 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); 6069 * Notification notif = new Notification.Builder(context) 6070 * .extend(new Notification.WearableExtender() 6071 * .setDisplayIntent(displayPendingIntent) 6072 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 6073 * .build();</pre> 6074 * 6075 * <p>The activity to launch needs to allow embedding, must be exported, and 6076 * should have an empty task affinity. It is also recommended to use the device 6077 * default light theme. 6078 * 6079 * <p>Example AndroidManifest.xml entry: 6080 * <pre class="prettyprint"> 6081 * <activity android:name="com.example.MyDisplayActivity" 6082 * android:exported="true" 6083 * android:allowEmbedded="true" 6084 * android:taskAffinity="" 6085 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 6086 * 6087 * @param intent the {@link PendingIntent} for an activity 6088 * @return this object for method chaining 6089 * @see android.app.Notification.WearableExtender#getDisplayIntent 6090 */ 6091 public WearableExtender setDisplayIntent(PendingIntent intent) { 6092 mDisplayIntent = intent; 6093 return this; 6094 } 6095 6096 /** 6097 * Get the intent to launch inside of an activity view when displaying this 6098 * notification. This {@code PendingIntent} should be for an activity. 6099 */ 6100 public PendingIntent getDisplayIntent() { 6101 return mDisplayIntent; 6102 } 6103 6104 /** 6105 * Add an additional page of content to display with this notification. The current 6106 * notification forms the first page, and pages added using this function form 6107 * subsequent pages. This field can be used to separate a notification into multiple 6108 * sections. 6109 * 6110 * @param page the notification to add as another page 6111 * @return this object for method chaining 6112 * @see android.app.Notification.WearableExtender#getPages 6113 */ 6114 public WearableExtender addPage(Notification page) { 6115 mPages.add(page); 6116 return this; 6117 } 6118 6119 /** 6120 * Add additional pages of content to display with this notification. The current 6121 * notification forms the first page, and pages added using this function form 6122 * subsequent pages. This field can be used to separate a notification into multiple 6123 * sections. 6124 * 6125 * @param pages a list of notifications 6126 * @return this object for method chaining 6127 * @see android.app.Notification.WearableExtender#getPages 6128 */ 6129 public WearableExtender addPages(List<Notification> pages) { 6130 mPages.addAll(pages); 6131 return this; 6132 } 6133 6134 /** 6135 * Clear all additional pages present on this builder. 6136 * @return this object for method chaining. 6137 * @see #addPage 6138 */ 6139 public WearableExtender clearPages() { 6140 mPages.clear(); 6141 return this; 6142 } 6143 6144 /** 6145 * Get the array of additional pages of content for displaying this notification. The 6146 * current notification forms the first page, and elements within this array form 6147 * subsequent pages. This field can be used to separate a notification into multiple 6148 * sections. 6149 * @return the pages for this notification 6150 */ 6151 public List<Notification> getPages() { 6152 return mPages; 6153 } 6154 6155 /** 6156 * Set a background image to be displayed behind the notification content. 6157 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 6158 * will work with any notification style. 6159 * 6160 * @param background the background bitmap 6161 * @return this object for method chaining 6162 * @see android.app.Notification.WearableExtender#getBackground 6163 */ 6164 public WearableExtender setBackground(Bitmap background) { 6165 mBackground = background; 6166 return this; 6167 } 6168 6169 /** 6170 * Get a background image to be displayed behind the notification content. 6171 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 6172 * will work with any notification style. 6173 * 6174 * @return the background image 6175 * @see android.app.Notification.WearableExtender#setBackground 6176 */ 6177 public Bitmap getBackground() { 6178 return mBackground; 6179 } 6180 6181 /** 6182 * Set an icon that goes with the content of this notification. 6183 */ 6184 public WearableExtender setContentIcon(int icon) { 6185 mContentIcon = icon; 6186 return this; 6187 } 6188 6189 /** 6190 * Get an icon that goes with the content of this notification. 6191 */ 6192 public int getContentIcon() { 6193 return mContentIcon; 6194 } 6195 6196 /** 6197 * Set the gravity that the content icon should have within the notification display. 6198 * Supported values include {@link android.view.Gravity#START} and 6199 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 6200 * @see #setContentIcon 6201 */ 6202 public WearableExtender setContentIconGravity(int contentIconGravity) { 6203 mContentIconGravity = contentIconGravity; 6204 return this; 6205 } 6206 6207 /** 6208 * Get the gravity that the content icon should have within the notification display. 6209 * Supported values include {@link android.view.Gravity#START} and 6210 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 6211 * @see #getContentIcon 6212 */ 6213 public int getContentIconGravity() { 6214 return mContentIconGravity; 6215 } 6216 6217 /** 6218 * Set an action from this notification's actions to be clickable with the content of 6219 * this notification. This action will no longer display separately from the 6220 * notification's content. 6221 * 6222 * <p>For notifications with multiple pages, child pages can also have content actions 6223 * set, although the list of available actions comes from the main notification and not 6224 * from the child page's notification. 6225 * 6226 * @param actionIndex The index of the action to hoist onto the current notification page. 6227 * If wearable actions were added to the main notification, this index 6228 * will apply to that list, otherwise it will apply to the regular 6229 * actions list. 6230 */ 6231 public WearableExtender setContentAction(int actionIndex) { 6232 mContentActionIndex = actionIndex; 6233 return this; 6234 } 6235 6236 /** 6237 * Get the index of the notification action, if any, that was specified as being clickable 6238 * with the content of this notification. This action will no longer display separately 6239 * from the notification's content. 6240 * 6241 * <p>For notifications with multiple pages, child pages can also have content actions 6242 * set, although the list of available actions comes from the main notification and not 6243 * from the child page's notification. 6244 * 6245 * <p>If wearable specific actions were added to the main notification, this index will 6246 * apply to that list, otherwise it will apply to the regular actions list. 6247 * 6248 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 6249 */ 6250 public int getContentAction() { 6251 return mContentActionIndex; 6252 } 6253 6254 /** 6255 * Set the gravity that this notification should have within the available viewport space. 6256 * Supported values include {@link android.view.Gravity#TOP}, 6257 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 6258 * The default value is {@link android.view.Gravity#BOTTOM}. 6259 */ 6260 public WearableExtender setGravity(int gravity) { 6261 mGravity = gravity; 6262 return this; 6263 } 6264 6265 /** 6266 * Get the gravity that this notification should have within the available viewport space. 6267 * Supported values include {@link android.view.Gravity#TOP}, 6268 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 6269 * The default value is {@link android.view.Gravity#BOTTOM}. 6270 */ 6271 public int getGravity() { 6272 return mGravity; 6273 } 6274 6275 /** 6276 * Set the custom size preset for the display of this notification out of the available 6277 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 6278 * {@link #SIZE_LARGE}. 6279 * <p>Some custom size presets are only applicable for custom display notifications created 6280 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 6281 * documentation for the preset in question. See also 6282 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 6283 */ 6284 public WearableExtender setCustomSizePreset(int sizePreset) { 6285 mCustomSizePreset = sizePreset; 6286 return this; 6287 } 6288 6289 /** 6290 * Get the custom size preset for the display of this notification out of the available 6291 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 6292 * {@link #SIZE_LARGE}. 6293 * <p>Some custom size presets are only applicable for custom display notifications created 6294 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 6295 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 6296 */ 6297 public int getCustomSizePreset() { 6298 return mCustomSizePreset; 6299 } 6300 6301 /** 6302 * Set the custom height in pixels for the display of this notification's content. 6303 * <p>This option is only available for custom display notifications created 6304 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 6305 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 6306 * {@link #getCustomContentHeight}. 6307 */ 6308 public WearableExtender setCustomContentHeight(int height) { 6309 mCustomContentHeight = height; 6310 return this; 6311 } 6312 6313 /** 6314 * Get the custom height in pixels for the display of this notification's content. 6315 * <p>This option is only available for custom display notifications created 6316 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 6317 * {@link #setCustomContentHeight}. 6318 */ 6319 public int getCustomContentHeight() { 6320 return mCustomContentHeight; 6321 } 6322 6323 /** 6324 * Set whether the scrolling position for the contents of this notification should start 6325 * at the bottom of the contents instead of the top when the contents are too long to 6326 * display within the screen. Default is false (start scroll at the top). 6327 */ 6328 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 6329 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 6330 return this; 6331 } 6332 6333 /** 6334 * Get whether the scrolling position for the contents of this notification should start 6335 * at the bottom of the contents instead of the top when the contents are too long to 6336 * display within the screen. Default is false (start scroll at the top). 6337 */ 6338 public boolean getStartScrollBottom() { 6339 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 6340 } 6341 6342 /** 6343 * Set whether the content intent is available when the wearable device is not connected 6344 * to a companion device. The user can still trigger this intent when the wearable device 6345 * is offline, but a visual hint will indicate that the content intent may not be available. 6346 * Defaults to true. 6347 */ 6348 public WearableExtender setContentIntentAvailableOffline( 6349 boolean contentIntentAvailableOffline) { 6350 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 6351 return this; 6352 } 6353 6354 /** 6355 * Get whether the content intent is available when the wearable device is not connected 6356 * to a companion device. The user can still trigger this intent when the wearable device 6357 * is offline, but a visual hint will indicate that the content intent may not be available. 6358 * Defaults to true. 6359 */ 6360 public boolean getContentIntentAvailableOffline() { 6361 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 6362 } 6363 6364 /** 6365 * Set a hint that this notification's icon should not be displayed. 6366 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 6367 * @return this object for method chaining 6368 */ 6369 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 6370 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 6371 return this; 6372 } 6373 6374 /** 6375 * Get a hint that this notification's icon should not be displayed. 6376 * @return {@code true} if this icon should not be displayed, false otherwise. 6377 * The default value is {@code false} if this was never set. 6378 */ 6379 public boolean getHintHideIcon() { 6380 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 6381 } 6382 6383 /** 6384 * Set a visual hint that only the background image of this notification should be 6385 * displayed, and other semantic content should be hidden. This hint is only applicable 6386 * to sub-pages added using {@link #addPage}. 6387 */ 6388 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 6389 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 6390 return this; 6391 } 6392 6393 /** 6394 * Get a visual hint that only the background image of this notification should be 6395 * displayed, and other semantic content should be hidden. This hint is only applicable 6396 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 6397 */ 6398 public boolean getHintShowBackgroundOnly() { 6399 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 6400 } 6401 6402 /** 6403 * Set a hint that this notification's background should not be clipped if possible, 6404 * and should instead be resized to fully display on the screen, retaining the aspect 6405 * ratio of the image. This can be useful for images like barcodes or qr codes. 6406 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 6407 * @return this object for method chaining 6408 */ 6409 public WearableExtender setHintAvoidBackgroundClipping( 6410 boolean hintAvoidBackgroundClipping) { 6411 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 6412 return this; 6413 } 6414 6415 /** 6416 * Get a hint that this notification's background should not be clipped if possible, 6417 * and should instead be resized to fully display on the screen, retaining the aspect 6418 * ratio of the image. This can be useful for images like barcodes or qr codes. 6419 * @return {@code true} if it's ok if the background is clipped on the screen, false 6420 * otherwise. The default value is {@code false} if this was never set. 6421 */ 6422 public boolean getHintAvoidBackgroundClipping() { 6423 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 6424 } 6425 6426 /** 6427 * Set a hint that the screen should remain on for at least this duration when 6428 * this notification is displayed on the screen. 6429 * @param timeout The requested screen timeout in milliseconds. Can also be either 6430 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 6431 * @return this object for method chaining 6432 */ 6433 public WearableExtender setHintScreenTimeout(int timeout) { 6434 mHintScreenTimeout = timeout; 6435 return this; 6436 } 6437 6438 /** 6439 * Get the duration, in milliseconds, that the screen should remain on for 6440 * when this notification is displayed. 6441 * @return the duration in milliseconds if > 0, or either one of the sentinel values 6442 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 6443 */ 6444 public int getHintScreenTimeout() { 6445 return mHintScreenTimeout; 6446 } 6447 6448 /** 6449 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 6450 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 6451 * qr codes, as well as other simple black-and-white tickets. 6452 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 6453 * @return this object for method chaining 6454 */ 6455 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 6456 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 6457 return this; 6458 } 6459 6460 /** 6461 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 6462 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 6463 * qr codes, as well as other simple black-and-white tickets. 6464 * @return {@code true} if it should be displayed in ambient, false otherwise 6465 * otherwise. The default value is {@code false} if this was never set. 6466 */ 6467 public boolean getHintAmbientBigPicture() { 6468 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 6469 } 6470 6471 /** 6472 * Set a hint that this notification's content intent will launch an {@link Activity} 6473 * directly, telling the platform that it can generate the appropriate transitions. 6474 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 6475 * an activity and transitions should be generated, false otherwise. 6476 * @return this object for method chaining 6477 */ 6478 public WearableExtender setHintContentIntentLaunchesActivity( 6479 boolean hintContentIntentLaunchesActivity) { 6480 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 6481 return this; 6482 } 6483 6484 /** 6485 * Get a hint that this notification's content intent will launch an {@link Activity} 6486 * directly, telling the platform that it can generate the appropriate transitions 6487 * @return {@code true} if the content intent will launch an activity and transitions should 6488 * be generated, false otherwise. The default value is {@code false} if this was never set. 6489 */ 6490 public boolean getHintContentIntentLaunchesActivity() { 6491 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 6492 } 6493 6494 /** 6495 * Sets the dismissal id for this notification. If a notification is posted with a 6496 * dismissal id, then when that notification is canceled, notifications on other wearables 6497 * and the paired Android phone having that same dismissal id will also be canceled. See 6498 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 6499 * Notifications</a> for more information. 6500 * @param dismissalId the dismissal id of the notification. 6501 * @return this object for method chaining 6502 */ 6503 public WearableExtender setDismissalId(String dismissalId) { 6504 mDismissalId = dismissalId; 6505 return this; 6506 } 6507 6508 /** 6509 * Returns the dismissal id of the notification. 6510 * @return the dismissal id of the notification or null if it has not been set. 6511 */ 6512 public String getDismissalId() { 6513 return mDismissalId; 6514 } 6515 6516 /** 6517 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 6518 * posted from a phone to provide finer-grained control on what notifications are bridged 6519 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 6520 * Features to Notifications</a> for more information. 6521 * @param bridgeTag the bridge tag of the notification. 6522 * @return this object for method chaining 6523 */ 6524 public WearableExtender setBridgeTag(String bridgeTag) { 6525 mBridgeTag = bridgeTag; 6526 return this; 6527 } 6528 6529 /** 6530 * Returns the bridge tag of the notification. 6531 * @return the bridge tag or null if not present. 6532 */ 6533 public String getBridgeTag() { 6534 return mBridgeTag; 6535 } 6536 6537 private void setFlag(int mask, boolean value) { 6538 if (value) { 6539 mFlags |= mask; 6540 } else { 6541 mFlags &= ~mask; 6542 } 6543 } 6544 } 6545 6546 /** 6547 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 6548 * with car extensions: 6549 * 6550 * <ol> 6551 * <li>Create an {@link Notification.Builder}, setting any desired 6552 * properties. 6553 * <li>Create a {@link CarExtender}. 6554 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 6555 * {@link CarExtender}. 6556 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 6557 * to apply the extensions to a notification. 6558 * </ol> 6559 * 6560 * <pre class="prettyprint"> 6561 * Notification notification = new Notification.Builder(context) 6562 * ... 6563 * .extend(new CarExtender() 6564 * .set*(...)) 6565 * .build(); 6566 * </pre> 6567 * 6568 * <p>Car extensions can be accessed on an existing notification by using the 6569 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 6570 * to access values. 6571 */ 6572 public static final class CarExtender implements Extender { 6573 private static final String TAG = "CarExtender"; 6574 6575 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 6576 private static final String EXTRA_LARGE_ICON = "large_icon"; 6577 private static final String EXTRA_CONVERSATION = "car_conversation"; 6578 private static final String EXTRA_COLOR = "app_color"; 6579 6580 private Bitmap mLargeIcon; 6581 private UnreadConversation mUnreadConversation; 6582 private int mColor = Notification.COLOR_DEFAULT; 6583 6584 /** 6585 * Create a {@link CarExtender} with default options. 6586 */ 6587 public CarExtender() { 6588 } 6589 6590 /** 6591 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 6592 * 6593 * @param notif The notification from which to copy options. 6594 */ 6595 public CarExtender(Notification notif) { 6596 Bundle carBundle = notif.extras == null ? 6597 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 6598 if (carBundle != null) { 6599 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 6600 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 6601 6602 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 6603 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 6604 } 6605 } 6606 6607 /** 6608 * Apply car extensions to a notification that is being built. This is typically called by 6609 * the {@link Notification.Builder#extend(Notification.Extender)} 6610 * method of {@link Notification.Builder}. 6611 */ 6612 @Override 6613 public Notification.Builder extend(Notification.Builder builder) { 6614 Bundle carExtensions = new Bundle(); 6615 6616 if (mLargeIcon != null) { 6617 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 6618 } 6619 if (mColor != Notification.COLOR_DEFAULT) { 6620 carExtensions.putInt(EXTRA_COLOR, mColor); 6621 } 6622 6623 if (mUnreadConversation != null) { 6624 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 6625 carExtensions.putBundle(EXTRA_CONVERSATION, b); 6626 } 6627 6628 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 6629 return builder; 6630 } 6631 6632 /** 6633 * Sets the accent color to use when Android Auto presents the notification. 6634 * 6635 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 6636 * to accent the displayed notification. However, not all colors are acceptable in an 6637 * automotive setting. This method can be used to override the color provided in the 6638 * notification in such a situation. 6639 */ 6640 public CarExtender setColor(@ColorInt int color) { 6641 mColor = color; 6642 return this; 6643 } 6644 6645 /** 6646 * Gets the accent color. 6647 * 6648 * @see #setColor 6649 */ 6650 @ColorInt 6651 public int getColor() { 6652 return mColor; 6653 } 6654 6655 /** 6656 * Sets the large icon of the car notification. 6657 * 6658 * If no large icon is set in the extender, Android Auto will display the icon 6659 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 6660 * 6661 * @param largeIcon The large icon to use in the car notification. 6662 * @return This object for method chaining. 6663 */ 6664 public CarExtender setLargeIcon(Bitmap largeIcon) { 6665 mLargeIcon = largeIcon; 6666 return this; 6667 } 6668 6669 /** 6670 * Gets the large icon used in this car notification, or null if no icon has been set. 6671 * 6672 * @return The large icon for the car notification. 6673 * @see CarExtender#setLargeIcon 6674 */ 6675 public Bitmap getLargeIcon() { 6676 return mLargeIcon; 6677 } 6678 6679 /** 6680 * Sets the unread conversation in a message notification. 6681 * 6682 * @param unreadConversation The unread part of the conversation this notification conveys. 6683 * @return This object for method chaining. 6684 */ 6685 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 6686 mUnreadConversation = unreadConversation; 6687 return this; 6688 } 6689 6690 /** 6691 * Returns the unread conversation conveyed by this notification. 6692 * @see #setUnreadConversation(UnreadConversation) 6693 */ 6694 public UnreadConversation getUnreadConversation() { 6695 return mUnreadConversation; 6696 } 6697 6698 /** 6699 * A class which holds the unread messages from a conversation. 6700 */ 6701 public static class UnreadConversation { 6702 private static final String KEY_AUTHOR = "author"; 6703 private static final String KEY_TEXT = "text"; 6704 private static final String KEY_MESSAGES = "messages"; 6705 private static final String KEY_REMOTE_INPUT = "remote_input"; 6706 private static final String KEY_ON_REPLY = "on_reply"; 6707 private static final String KEY_ON_READ = "on_read"; 6708 private static final String KEY_PARTICIPANTS = "participants"; 6709 private static final String KEY_TIMESTAMP = "timestamp"; 6710 6711 private final String[] mMessages; 6712 private final RemoteInput mRemoteInput; 6713 private final PendingIntent mReplyPendingIntent; 6714 private final PendingIntent mReadPendingIntent; 6715 private final String[] mParticipants; 6716 private final long mLatestTimestamp; 6717 6718 UnreadConversation(String[] messages, RemoteInput remoteInput, 6719 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 6720 String[] participants, long latestTimestamp) { 6721 mMessages = messages; 6722 mRemoteInput = remoteInput; 6723 mReadPendingIntent = readPendingIntent; 6724 mReplyPendingIntent = replyPendingIntent; 6725 mParticipants = participants; 6726 mLatestTimestamp = latestTimestamp; 6727 } 6728 6729 /** 6730 * Gets the list of messages conveyed by this notification. 6731 */ 6732 public String[] getMessages() { 6733 return mMessages; 6734 } 6735 6736 /** 6737 * Gets the remote input that will be used to convey the response to a message list, or 6738 * null if no such remote input exists. 6739 */ 6740 public RemoteInput getRemoteInput() { 6741 return mRemoteInput; 6742 } 6743 6744 /** 6745 * Gets the pending intent that will be triggered when the user replies to this 6746 * notification. 6747 */ 6748 public PendingIntent getReplyPendingIntent() { 6749 return mReplyPendingIntent; 6750 } 6751 6752 /** 6753 * Gets the pending intent that Android Auto will send after it reads aloud all messages 6754 * in this object's message list. 6755 */ 6756 public PendingIntent getReadPendingIntent() { 6757 return mReadPendingIntent; 6758 } 6759 6760 /** 6761 * Gets the participants in the conversation. 6762 */ 6763 public String[] getParticipants() { 6764 return mParticipants; 6765 } 6766 6767 /** 6768 * Gets the firs participant in the conversation. 6769 */ 6770 public String getParticipant() { 6771 return mParticipants.length > 0 ? mParticipants[0] : null; 6772 } 6773 6774 /** 6775 * Gets the timestamp of the conversation. 6776 */ 6777 public long getLatestTimestamp() { 6778 return mLatestTimestamp; 6779 } 6780 6781 Bundle getBundleForUnreadConversation() { 6782 Bundle b = new Bundle(); 6783 String author = null; 6784 if (mParticipants != null && mParticipants.length > 1) { 6785 author = mParticipants[0]; 6786 } 6787 Parcelable[] messages = new Parcelable[mMessages.length]; 6788 for (int i = 0; i < messages.length; i++) { 6789 Bundle m = new Bundle(); 6790 m.putString(KEY_TEXT, mMessages[i]); 6791 m.putString(KEY_AUTHOR, author); 6792 messages[i] = m; 6793 } 6794 b.putParcelableArray(KEY_MESSAGES, messages); 6795 if (mRemoteInput != null) { 6796 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 6797 } 6798 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 6799 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 6800 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 6801 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 6802 return b; 6803 } 6804 6805 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 6806 if (b == null) { 6807 return null; 6808 } 6809 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 6810 String[] messages = null; 6811 if (parcelableMessages != null) { 6812 String[] tmp = new String[parcelableMessages.length]; 6813 boolean success = true; 6814 for (int i = 0; i < tmp.length; i++) { 6815 if (!(parcelableMessages[i] instanceof Bundle)) { 6816 success = false; 6817 break; 6818 } 6819 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 6820 if (tmp[i] == null) { 6821 success = false; 6822 break; 6823 } 6824 } 6825 if (success) { 6826 messages = tmp; 6827 } else { 6828 return null; 6829 } 6830 } 6831 6832 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 6833 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 6834 6835 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 6836 6837 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 6838 if (participants == null || participants.length != 1) { 6839 return null; 6840 } 6841 6842 return new UnreadConversation(messages, 6843 remoteInput, 6844 onReply, 6845 onRead, 6846 participants, b.getLong(KEY_TIMESTAMP)); 6847 } 6848 }; 6849 6850 /** 6851 * Builder class for {@link CarExtender.UnreadConversation} objects. 6852 */ 6853 public static class Builder { 6854 private final List<String> mMessages = new ArrayList<String>(); 6855 private final String mParticipant; 6856 private RemoteInput mRemoteInput; 6857 private PendingIntent mReadPendingIntent; 6858 private PendingIntent mReplyPendingIntent; 6859 private long mLatestTimestamp; 6860 6861 /** 6862 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 6863 * 6864 * @param name The name of the other participant in the conversation. 6865 */ 6866 public Builder(String name) { 6867 mParticipant = name; 6868 } 6869 6870 /** 6871 * Appends a new unread message to the list of messages for this conversation. 6872 * 6873 * The messages should be added from oldest to newest. 6874 * 6875 * @param message The text of the new unread message. 6876 * @return This object for method chaining. 6877 */ 6878 public Builder addMessage(String message) { 6879 mMessages.add(message); 6880 return this; 6881 } 6882 6883 /** 6884 * Sets the pending intent and remote input which will convey the reply to this 6885 * notification. 6886 * 6887 * @param pendingIntent The pending intent which will be triggered on a reply. 6888 * @param remoteInput The remote input parcelable which will carry the reply. 6889 * @return This object for method chaining. 6890 * 6891 * @see CarExtender.UnreadConversation#getRemoteInput 6892 * @see CarExtender.UnreadConversation#getReplyPendingIntent 6893 */ 6894 public Builder setReplyAction( 6895 PendingIntent pendingIntent, RemoteInput remoteInput) { 6896 mRemoteInput = remoteInput; 6897 mReplyPendingIntent = pendingIntent; 6898 6899 return this; 6900 } 6901 6902 /** 6903 * Sets the pending intent that will be sent once the messages in this notification 6904 * are read. 6905 * 6906 * @param pendingIntent The pending intent to use. 6907 * @return This object for method chaining. 6908 */ 6909 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 6910 mReadPendingIntent = pendingIntent; 6911 return this; 6912 } 6913 6914 /** 6915 * Sets the timestamp of the most recent message in an unread conversation. 6916 * 6917 * If a messaging notification has been posted by your application and has not 6918 * yet been cancelled, posting a later notification with the same id and tag 6919 * but without a newer timestamp may result in Android Auto not displaying a 6920 * heads up notification for the later notification. 6921 * 6922 * @param timestamp The timestamp of the most recent message in the conversation. 6923 * @return This object for method chaining. 6924 */ 6925 public Builder setLatestTimestamp(long timestamp) { 6926 mLatestTimestamp = timestamp; 6927 return this; 6928 } 6929 6930 /** 6931 * Builds a new unread conversation object. 6932 * 6933 * @return The new unread conversation object. 6934 */ 6935 public UnreadConversation build() { 6936 String[] messages = mMessages.toArray(new String[mMessages.size()]); 6937 String[] participants = { mParticipant }; 6938 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 6939 mReadPendingIntent, participants, mLatestTimestamp); 6940 } 6941 } 6942 } 6943 6944 /** 6945 * Get an array of Notification objects from a parcelable array bundle field. 6946 * Update the bundle to have a typed array so fetches in the future don't need 6947 * to do an array copy. 6948 */ 6949 private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { 6950 Parcelable[] array = bundle.getParcelableArray(key); 6951 if (array instanceof Notification[] || array == null) { 6952 return (Notification[]) array; 6953 } 6954 Notification[] typedArray = Arrays.copyOf(array, array.length, 6955 Notification[].class); 6956 bundle.putParcelableArray(key, typedArray); 6957 return typedArray; 6958 } 6959 6960 private static class BuilderRemoteViews extends RemoteViews { 6961 public BuilderRemoteViews(Parcel parcel) { 6962 super(parcel); 6963 } 6964 6965 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 6966 super(appInfo, layoutId); 6967 } 6968 6969 @Override 6970 public BuilderRemoteViews clone() { 6971 Parcel p = Parcel.obtain(); 6972 writeToParcel(p, 0); 6973 p.setDataPosition(0); 6974 BuilderRemoteViews brv = new BuilderRemoteViews(p); 6975 p.recycle(); 6976 return brv; 6977 } 6978 } 6979} 6980