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 17 package android.app; 18 19 import android.annotation.ColorInt; 20 import android.annotation.DrawableRes; 21 import android.annotation.IntDef; 22 import android.annotation.SdkConstant; 23 import android.annotation.SdkConstant.SdkConstantType; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.ApplicationInfo; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.res.ColorStateList; 29 import android.graphics.Bitmap; 30 import android.graphics.Canvas; 31 import android.graphics.PorterDuff; 32 import android.graphics.drawable.Drawable; 33 import android.graphics.drawable.Icon; 34 import android.media.AudioAttributes; 35 import android.media.AudioManager; 36 import android.media.session.MediaSession; 37 import android.net.Uri; 38 import android.os.BadParcelableException; 39 import android.os.Build; 40 import android.os.Bundle; 41 import android.os.Parcel; 42 import android.os.Parcelable; 43 import android.os.SystemClock; 44 import android.os.UserHandle; 45 import android.text.TextUtils; 46 import android.util.Log; 47 import android.util.MathUtils; 48 import android.util.TypedValue; 49 import android.view.Gravity; 50 import android.view.View; 51 import android.widget.ProgressBar; 52 import android.widget.RemoteViews; 53 54 import com.android.internal.R; 55 import com.android.internal.util.NotificationColorUtil; 56 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.lang.reflect.Constructor; 60 import java.text.NumberFormat; 61 import java.util.ArrayList; 62 import java.util.Arrays; 63 import java.util.Collections; 64 import java.util.List; 65 66 /** 67 * A class that represents how a persistent notification is to be presented to 68 * the user using the {@link android.app.NotificationManager}. 69 * 70 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 71 * easier to construct Notifications.</p> 72 * 73 * <div class="special reference"> 74 * <h3>Developer Guides</h3> 75 * <p>For a guide to creating notifications, read the 76 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 77 * developer guide.</p> 78 * </div> 79 */ 80 public class Notification implements Parcelable 81 { 82 private static final String TAG = "Notification"; 83 84 /** 85 * An activity that provides a user interface for adjusting notification preferences for its 86 * containing application. Optional but recommended for apps that post 87 * {@link android.app.Notification Notifications}. 88 */ 89 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 90 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES 91 = "android.intent.category.NOTIFICATION_PREFERENCES"; 92 93 /** 94 * Use all default values (where applicable). 95 */ 96 public static final int DEFAULT_ALL = ~0; 97 98 /** 99 * Use the default notification sound. This will ignore any given 100 * {@link #sound}. 101 * 102 * <p> 103 * A notification that is noisy is more likely to be presented as a heads-up notification. 104 * </p> 105 * 106 * @see #defaults 107 */ 108 109 public static final int DEFAULT_SOUND = 1; 110 111 /** 112 * Use the default notification vibrate. This will ignore any given 113 * {@link #vibrate}. Using phone vibration requires the 114 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 115 * 116 * <p> 117 * A notification that vibrates is more likely to be presented as a heads-up notification. 118 * </p> 119 * 120 * @see #defaults 121 */ 122 123 public static final int DEFAULT_VIBRATE = 2; 124 125 /** 126 * Use the default notification lights. This will ignore the 127 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 128 * {@link #ledOnMS}. 129 * 130 * @see #defaults 131 */ 132 133 public static final int DEFAULT_LIGHTS = 4; 134 135 /** 136 * Maximum length of CharSequences accepted by Builder and friends. 137 * 138 * <p> 139 * Avoids spamming the system with overly large strings such as full e-mails. 140 */ 141 private static final int MAX_CHARSEQUENCE_LENGTH = 5 * 1024; 142 143 /** 144 * A timestamp related to this notification, in milliseconds since the epoch. 145 * 146 * Default value: {@link System#currentTimeMillis() Now}. 147 * 148 * Choose a timestamp that will be most relevant to the user. For most finite events, this 149 * corresponds to the time the event happened (or will happen, in the case of events that have 150 * yet to occur but about which the user is being informed). Indefinite events should be 151 * timestamped according to when the activity began. 152 * 153 * Some examples: 154 * 155 * <ul> 156 * <li>Notification of a new chat message should be stamped when the message was received.</li> 157 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 158 * <li>Notification of a completed file download should be stamped when the download finished.</li> 159 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 160 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 161 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 162 * </ul> 163 * 164 */ 165 public long when; 166 167 /** 168 * The resource id of a drawable to use as the icon in the status bar. 169 * 170 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead. 171 */ 172 @Deprecated 173 @DrawableRes 174 public int icon; 175 176 /** 177 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 178 * leave it at its default value of 0. 179 * 180 * @see android.widget.ImageView#setImageLevel 181 * @see android.graphics.drawable.Drawable#setLevel 182 */ 183 public int iconLevel; 184 185 /** 186 * The number of events that this notification represents. For example, in a new mail 187 * notification, this could be the number of unread messages. 188 * 189 * The system may or may not use this field to modify the appearance of the notification. For 190 * example, before {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this number was 191 * superimposed over the icon in the status bar. Starting with 192 * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, the template used by 193 * {@link Notification.Builder} has displayed the number in the expanded notification view. 194 * 195 * If the number is 0 or negative, it is never shown. 196 */ 197 public int number; 198 199 /** 200 * The intent to execute when the expanded status entry is clicked. If 201 * this is an activity, it must include the 202 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 203 * that you take care of task management as described in the 204 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 205 * Stack</a> document. In particular, make sure to read the notification section 206 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling 207 * Notifications</a> for the correct ways to launch an application from a 208 * notification. 209 */ 210 public PendingIntent contentIntent; 211 212 /** 213 * The intent to execute when the notification is explicitly dismissed by the user, either with 214 * the "Clear All" button or by swiping it away individually. 215 * 216 * This probably shouldn't be launching an activity since several of those will be sent 217 * at the same time. 218 */ 219 public PendingIntent deleteIntent; 220 221 /** 222 * An intent to launch instead of posting the notification to the status bar. 223 * 224 * <p> 225 * The system UI may choose to display a heads-up notification, instead of 226 * launching this intent, while the user is using the device. 227 * </p> 228 * 229 * @see Notification.Builder#setFullScreenIntent 230 */ 231 public PendingIntent fullScreenIntent; 232 233 /** 234 * Text that summarizes this notification for accessibility services. 235 * 236 * As of the L release, this text is no longer shown on screen, but it is still useful to 237 * accessibility services (where it serves as an audible announcement of the notification's 238 * appearance). 239 * 240 * @see #tickerView 241 */ 242 public CharSequence tickerText; 243 244 /** 245 * Formerly, a view showing the {@link #tickerText}. 246 * 247 * No longer displayed in the status bar as of API 21. 248 */ 249 @Deprecated 250 public RemoteViews tickerView; 251 252 /** 253 * The view that will represent this notification in the expanded status bar. 254 */ 255 public RemoteViews contentView; 256 257 /** 258 * A large-format version of {@link #contentView}, giving the Notification an 259 * opportunity to show more detail. The system UI may choose to show this 260 * instead of the normal content view at its discretion. 261 */ 262 public RemoteViews bigContentView; 263 264 265 /** 266 * A medium-format version of {@link #contentView}, providing the Notification an 267 * opportunity to add action buttons to contentView. At its discretion, the system UI may 268 * choose to show this as a heads-up notification, which will pop up so the user can see 269 * it without leaving their current activity. 270 */ 271 public RemoteViews headsUpContentView; 272 273 /** 274 * A large bitmap to be shown in the notification content area. 275 * 276 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead. 277 */ 278 @Deprecated 279 public Bitmap largeIcon; 280 281 /** 282 * The sound to play. 283 * 284 * <p> 285 * A notification that is noisy is more likely to be presented as a heads-up notification. 286 * </p> 287 * 288 * <p> 289 * To play the default notification sound, see {@link #defaults}. 290 * </p> 291 */ 292 public Uri sound; 293 294 /** 295 * Use this constant as the value for audioStreamType to request that 296 * the default stream type for notifications be used. Currently the 297 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 298 * 299 * @deprecated Use {@link #audioAttributes} instead. 300 */ 301 @Deprecated 302 public static final int STREAM_DEFAULT = -1; 303 304 /** 305 * The audio stream type to use when playing the sound. 306 * Should be one of the STREAM_ constants from 307 * {@link android.media.AudioManager}. 308 * 309 * @deprecated Use {@link #audioAttributes} instead. 310 */ 311 @Deprecated 312 public int audioStreamType = STREAM_DEFAULT; 313 314 /** 315 * The default value of {@link #audioAttributes}. 316 */ 317 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() 318 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 319 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 320 .build(); 321 322 /** 323 * The {@link AudioAttributes audio attributes} to use when playing the sound. 324 */ 325 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 326 327 /** 328 * The pattern with which to vibrate. 329 * 330 * <p> 331 * To vibrate the default pattern, see {@link #defaults}. 332 * </p> 333 * 334 * <p> 335 * A notification that vibrates is more likely to be presented as a heads-up notification. 336 * </p> 337 * 338 * @see android.os.Vibrator#vibrate(long[],int) 339 */ 340 public long[] vibrate; 341 342 /** 343 * The color of the led. The hardware will do its best approximation. 344 * 345 * @see #FLAG_SHOW_LIGHTS 346 * @see #flags 347 */ 348 @ColorInt 349 public int ledARGB; 350 351 /** 352 * The number of milliseconds for the LED to be on while it's flashing. 353 * The hardware will do its best approximation. 354 * 355 * @see #FLAG_SHOW_LIGHTS 356 * @see #flags 357 */ 358 public int ledOnMS; 359 360 /** 361 * The number of milliseconds for the LED to be off while it's flashing. 362 * The hardware will do its best approximation. 363 * 364 * @see #FLAG_SHOW_LIGHTS 365 * @see #flags 366 */ 367 public int ledOffMS; 368 369 /** 370 * Specifies which values should be taken from the defaults. 371 * <p> 372 * To set, OR the desired from {@link #DEFAULT_SOUND}, 373 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 374 * values, use {@link #DEFAULT_ALL}. 375 * </p> 376 */ 377 public int defaults; 378 379 /** 380 * Bit to be bitwise-ored into the {@link #flags} field that should be 381 * set if you want the LED on for this notification. 382 * <ul> 383 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 384 * or 0 for both ledOnMS and ledOffMS.</li> 385 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 386 * <li>To flash the LED, pass the number of milliseconds that it should 387 * be on and off to ledOnMS and ledOffMS.</li> 388 * </ul> 389 * <p> 390 * Since hardware varies, you are not guaranteed that any of the values 391 * you pass are honored exactly. Use the system defaults (TODO) if possible 392 * because they will be set to values that work on any given hardware. 393 * <p> 394 * The alpha channel must be set for forward compatibility. 395 * 396 */ 397 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 398 399 /** 400 * Bit to be bitwise-ored into the {@link #flags} field that should be 401 * set if this notification is in reference to something that is ongoing, 402 * like a phone call. It should not be set if this notification is in 403 * reference to something that happened at a particular point in time, 404 * like a missed phone call. 405 */ 406 public static final int FLAG_ONGOING_EVENT = 0x00000002; 407 408 /** 409 * Bit to be bitwise-ored into the {@link #flags} field that if set, 410 * the audio will be repeated until the notification is 411 * cancelled or the notification window is opened. 412 */ 413 public static final int FLAG_INSISTENT = 0x00000004; 414 415 /** 416 * Bit to be bitwise-ored into the {@link #flags} field that should be 417 * set if you would only like the sound, vibrate and ticker to be played 418 * if the notification was not already showing. 419 */ 420 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 421 422 /** 423 * Bit to be bitwise-ored into the {@link #flags} field that should be 424 * set if the notification should be canceled when it is clicked by the 425 * user. 426 */ 427 public static final int FLAG_AUTO_CANCEL = 0x00000010; 428 429 /** 430 * Bit to be bitwise-ored into the {@link #flags} field that should be 431 * set if the notification should not be canceled when the user clicks 432 * the Clear all button. 433 */ 434 public static final int FLAG_NO_CLEAR = 0x00000020; 435 436 /** 437 * Bit to be bitwise-ored into the {@link #flags} field that should be 438 * set if this notification represents a currently running service. This 439 * will normally be set for you by {@link Service#startForeground}. 440 */ 441 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 442 443 /** 444 * Obsolete flag indicating high-priority notifications; use the priority field instead. 445 * 446 * @deprecated Use {@link #priority} with a positive value. 447 */ 448 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 449 450 /** 451 * Bit to be bitswise-ored into the {@link #flags} field that should be 452 * set if this notification is relevant to the current device only 453 * and it is not recommended that it bridge to other devices. 454 */ 455 public static final int FLAG_LOCAL_ONLY = 0x00000100; 456 457 /** 458 * Bit to be bitswise-ored into the {@link #flags} field that should be 459 * set if this notification is the group summary for a group of notifications. 460 * Grouped notifications may display in a cluster or stack on devices which 461 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 462 */ 463 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 464 465 public int flags; 466 467 /** @hide */ 468 @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX}) 469 @Retention(RetentionPolicy.SOURCE) 470 public @interface Priority {} 471 472 /** 473 * Default notification {@link #priority}. If your application does not prioritize its own 474 * notifications, use this value for all notifications. 475 */ 476 public static final int PRIORITY_DEFAULT = 0; 477 478 /** 479 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 480 * items smaller, or at a different position in the list, compared with your app's 481 * {@link #PRIORITY_DEFAULT} items. 482 */ 483 public static final int PRIORITY_LOW = -1; 484 485 /** 486 * Lowest {@link #priority}; these items might not be shown to the user except under special 487 * circumstances, such as detailed notification logs. 488 */ 489 public static final int PRIORITY_MIN = -2; 490 491 /** 492 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 493 * show these items larger, or at a different position in notification lists, compared with 494 * your app's {@link #PRIORITY_DEFAULT} items. 495 */ 496 public static final int PRIORITY_HIGH = 1; 497 498 /** 499 * Highest {@link #priority}, for your application's most important items that require the 500 * user's prompt attention or input. 501 */ 502 public static final int PRIORITY_MAX = 2; 503 504 /** 505 * Relative priority for this notification. 506 * 507 * Priority is an indication of how much of the user's valuable attention should be consumed by 508 * this notification. Low-priority notifications may be hidden from the user in certain 509 * situations, while the user might be interrupted for a higher-priority notification. The 510 * system will make a determination about how to interpret this priority when presenting 511 * the notification. 512 * 513 * <p> 514 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 515 * as a heads-up notification. 516 * </p> 517 * 518 */ 519 @Priority 520 public int priority; 521 522 /** 523 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 524 * to be applied by the standard Style templates when presenting this notification. 525 * 526 * The current template design constructs a colorful header image by overlaying the 527 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 528 * ignored. 529 */ 530 @ColorInt 531 public int color = COLOR_DEFAULT; 532 533 /** 534 * Special value of {@link #color} telling the system not to decorate this notification with 535 * any special color but instead use default colors when presenting this notification. 536 */ 537 @ColorInt 538 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 539 540 /** 541 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 542 * the notification's presence and contents in untrusted situations (namely, on the secure 543 * lockscreen). 544 * 545 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 546 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 547 * shown in all situations, but the contents are only available if the device is unlocked for 548 * the appropriate user. 549 * 550 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 551 * can be read even in an "insecure" context (that is, above a secure lockscreen). 552 * To modify the public version of this notification—for example, to redact some portions—see 553 * {@link Builder#setPublicVersion(Notification)}. 554 * 555 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 556 * and ticker until the user has bypassed the lockscreen. 557 */ 558 public int visibility; 559 560 /** 561 * Notification visibility: Show this notification in its entirety on all lockscreens. 562 * 563 * {@see #visibility} 564 */ 565 public static final int VISIBILITY_PUBLIC = 1; 566 567 /** 568 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 569 * private information on secure lockscreens. 570 * 571 * {@see #visibility} 572 */ 573 public static final int VISIBILITY_PRIVATE = 0; 574 575 /** 576 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 577 * 578 * {@see #visibility} 579 */ 580 public static final int VISIBILITY_SECRET = -1; 581 582 /** 583 * Notification category: incoming call (voice or video) or similar synchronous communication request. 584 */ 585 public static final String CATEGORY_CALL = "call"; 586 587 /** 588 * Notification category: incoming direct message (SMS, instant message, etc.). 589 */ 590 public static final String CATEGORY_MESSAGE = "msg"; 591 592 /** 593 * Notification category: asynchronous bulk message (email). 594 */ 595 public static final String CATEGORY_EMAIL = "email"; 596 597 /** 598 * Notification category: calendar event. 599 */ 600 public static final String CATEGORY_EVENT = "event"; 601 602 /** 603 * Notification category: promotion or advertisement. 604 */ 605 public static final String CATEGORY_PROMO = "promo"; 606 607 /** 608 * Notification category: alarm or timer. 609 */ 610 public static final String CATEGORY_ALARM = "alarm"; 611 612 /** 613 * Notification category: progress of a long-running background operation. 614 */ 615 public static final String CATEGORY_PROGRESS = "progress"; 616 617 /** 618 * Notification category: social network or sharing update. 619 */ 620 public static final String CATEGORY_SOCIAL = "social"; 621 622 /** 623 * Notification category: error in background operation or authentication status. 624 */ 625 public static final String CATEGORY_ERROR = "err"; 626 627 /** 628 * Notification category: media transport control for playback. 629 */ 630 public static final String CATEGORY_TRANSPORT = "transport"; 631 632 /** 633 * Notification category: system or device status update. Reserved for system use. 634 */ 635 public static final String CATEGORY_SYSTEM = "sys"; 636 637 /** 638 * Notification category: indication of running background service. 639 */ 640 public static final String CATEGORY_SERVICE = "service"; 641 642 /** 643 * Notification category: a specific, timely recommendation for a single thing. 644 * For example, a news app might want to recommend a news story it believes the user will 645 * want to read next. 646 */ 647 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 648 649 /** 650 * Notification category: ongoing information about device or contextual status. 651 */ 652 public static final String CATEGORY_STATUS = "status"; 653 654 /** 655 * Notification category: user-scheduled reminder. 656 */ 657 public static final String CATEGORY_REMINDER = "reminder"; 658 659 /** 660 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 661 * that best describes this Notification. May be used by the system for ranking and filtering. 662 */ 663 public String category; 664 665 private String mGroupKey; 666 667 /** 668 * Get the key used to group this notification into a cluster or stack 669 * with other notifications on devices which support such rendering. 670 */ getGroup()671 public String getGroup() { 672 return mGroupKey; 673 } 674 675 private String mSortKey; 676 677 /** 678 * Get a sort key that orders this notification among other notifications from the 679 * same package. This can be useful if an external sort was already applied and an app 680 * would like to preserve this. Notifications will be sorted lexicographically using this 681 * value, although providing different priorities in addition to providing sort key may 682 * cause this value to be ignored. 683 * 684 * <p>This sort key can also be used to order members of a notification group. See 685 * {@link Builder#setGroup}. 686 * 687 * @see String#compareTo(String) 688 */ getSortKey()689 public String getSortKey() { 690 return mSortKey; 691 } 692 693 /** 694 * Additional semantic data to be carried around with this Notification. 695 * <p> 696 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 697 * APIs, and are intended to be used by 698 * {@link android.service.notification.NotificationListenerService} implementations to extract 699 * detailed information from notification objects. 700 */ 701 public Bundle extras = new Bundle(); 702 703 /** 704 * {@link #extras} key: this is the title of the notification, 705 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 706 */ 707 public static final String EXTRA_TITLE = "android.title"; 708 709 /** 710 * {@link #extras} key: this is the title of the notification when shown in expanded form, 711 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 712 */ 713 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 714 715 /** 716 * {@link #extras} key: this is the main text payload, as supplied to 717 * {@link Builder#setContentText(CharSequence)}. 718 */ 719 public static final String EXTRA_TEXT = "android.text"; 720 721 /** 722 * {@link #extras} key: this is a third line of text, as supplied to 723 * {@link Builder#setSubText(CharSequence)}. 724 */ 725 public static final String EXTRA_SUB_TEXT = "android.subText"; 726 727 /** 728 * {@link #extras} key: this is a small piece of additional text as supplied to 729 * {@link Builder#setContentInfo(CharSequence)}. 730 */ 731 public static final String EXTRA_INFO_TEXT = "android.infoText"; 732 733 /** 734 * {@link #extras} key: this is a line of summary information intended to be shown 735 * alongside expanded notifications, as supplied to (e.g.) 736 * {@link BigTextStyle#setSummaryText(CharSequence)}. 737 */ 738 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 739 740 /** 741 * {@link #extras} key: this is the longer text shown in the big form of a 742 * {@link BigTextStyle} notification, as supplied to 743 * {@link BigTextStyle#bigText(CharSequence)}. 744 */ 745 public static final String EXTRA_BIG_TEXT = "android.bigText"; 746 747 /** 748 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 749 * supplied to {@link Builder#setSmallIcon(int)}. 750 */ 751 public static final String EXTRA_SMALL_ICON = "android.icon"; 752 753 /** 754 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 755 * notification payload, as 756 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 757 */ 758 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 759 760 /** 761 * {@link #extras} key: this is a bitmap to be used instead of the one from 762 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 763 * shown in its expanded form, as supplied to 764 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 765 */ 766 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 767 768 /** 769 * {@link #extras} key: this is the progress value supplied to 770 * {@link Builder#setProgress(int, int, boolean)}. 771 */ 772 public static final String EXTRA_PROGRESS = "android.progress"; 773 774 /** 775 * {@link #extras} key: this is the maximum value supplied to 776 * {@link Builder#setProgress(int, int, boolean)}. 777 */ 778 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 779 780 /** 781 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 782 * {@link Builder#setProgress(int, int, boolean)}. 783 */ 784 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 785 786 /** 787 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 788 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 789 * {@link Builder#setUsesChronometer(boolean)}. 790 */ 791 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 792 793 /** 794 * {@link #extras} key: whether {@link #when} should be shown, 795 * as supplied to {@link Builder#setShowWhen(boolean)}. 796 */ 797 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 798 799 /** 800 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 801 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 802 */ 803 public static final String EXTRA_PICTURE = "android.picture"; 804 805 /** 806 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 807 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 808 */ 809 public static final String EXTRA_TEXT_LINES = "android.textLines"; 810 811 /** 812 * {@link #extras} key: A string representing the name of the specific 813 * {@link android.app.Notification.Style} used to create this notification. 814 */ 815 public static final String EXTRA_TEMPLATE = "android.template"; 816 817 /** 818 * {@link #extras} key: A String array containing the people that this notification relates to, 819 * each of which was supplied to {@link Builder#addPerson(String)}. 820 */ 821 public static final String EXTRA_PEOPLE = "android.people"; 822 823 /** 824 * {@link #extras} key: used to provide hints about the appropriateness of 825 * displaying this notification as a heads-up notification. 826 * @hide 827 */ 828 public static final String EXTRA_AS_HEADS_UP = "headsup"; 829 830 /** 831 * Allow certain system-generated notifications to appear before the device is provisioned. 832 * Only available to notifications coming from the android package. 833 * @hide 834 */ 835 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 836 837 /** 838 * {@link #extras} key: A 839 * {@link android.content.ContentUris content URI} pointing to an image that can be displayed 840 * in the background when the notification is selected. The URI must point to an image stream 841 * suitable for passing into 842 * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 843 * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider 844 * URI used for this purpose must require no permissions to read the image data. 845 */ 846 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 847 848 /** 849 * {@link #extras} key: A 850 * {@link android.media.session.MediaSession.Token} associated with a 851 * {@link android.app.Notification.MediaStyle} notification. 852 */ 853 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 854 855 /** 856 * {@link #extras} key: the indices of actions to be shown in the compact view, 857 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 858 */ 859 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 860 861 /** 862 * {@link #extras} key: the user that built the notification. 863 * 864 * @hide 865 */ 866 public static final String EXTRA_ORIGINATING_USERID = "android.originatingUserId"; 867 868 /** 869 * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be 870 * displayed in the heads up space. 871 * 872 * <p> 873 * If this notification has a {@link #fullScreenIntent}, then it will always launch the 874 * full-screen intent when posted. 875 * </p> 876 * @hide 877 */ 878 public static final int HEADS_UP_NEVER = 0; 879 880 /** 881 * Default value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification may be 882 * displayed as a heads up. 883 * @hide 884 */ 885 public static final int HEADS_UP_ALLOWED = 1; 886 887 /** 888 * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification is a 889 * good candidate for display as a heads up. 890 * @hide 891 */ 892 public static final int HEADS_UP_REQUESTED = 2; 893 894 private Icon mSmallIcon; 895 private Icon mLargeIcon; 896 897 /** 898 * Structure to encapsulate a named action that can be shown as part of this notification. 899 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 900 * selected by the user. 901 * <p> 902 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 903 * or {@link Notification.Builder#addAction(Notification.Action)} 904 * to attach actions. 905 */ 906 public static class Action implements Parcelable { 907 private final Bundle mExtras; 908 private Icon mIcon; 909 private final RemoteInput[] mRemoteInputs; 910 911 /** 912 * Small icon representing the action. 913 * 914 * @deprecated Use {@link Action#getIcon()} instead. 915 */ 916 @Deprecated 917 public int icon; 918 919 /** 920 * Title of the action. 921 */ 922 public CharSequence title; 923 924 /** 925 * Intent to send when the user invokes this action. May be null, in which case the action 926 * may be rendered in a disabled presentation by the system UI. 927 */ 928 public PendingIntent actionIntent; 929 Action(Parcel in)930 private Action(Parcel in) { 931 if (in.readInt() != 0) { 932 mIcon = Icon.CREATOR.createFromParcel(in); 933 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 934 icon = mIcon.getResId(); 935 } 936 } 937 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 938 if (in.readInt() == 1) { 939 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 940 } 941 mExtras = in.readBundle(); 942 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 943 } 944 945 /** 946 * @deprecated Use {@link android.app.Notification.Action.Builder}. 947 */ 948 @Deprecated Action(int icon, CharSequence title, PendingIntent intent)949 public Action(int icon, CharSequence title, PendingIntent intent) { 950 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null); 951 } 952 Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs)953 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 954 RemoteInput[] remoteInputs) { 955 this.mIcon = icon; 956 this.title = title; 957 this.actionIntent = intent; 958 this.mExtras = extras != null ? extras : new Bundle(); 959 this.mRemoteInputs = remoteInputs; 960 } 961 962 /** 963 * Return an icon representing the action. 964 */ getIcon()965 public Icon getIcon() { 966 if (mIcon == null && icon != 0) { 967 // you snuck an icon in here without using the builder; let's try to keep it 968 mIcon = Icon.createWithResource("", icon); 969 } 970 return mIcon; 971 } 972 973 /** 974 * Get additional metadata carried around with this Action. 975 */ getExtras()976 public Bundle getExtras() { 977 return mExtras; 978 } 979 980 /** 981 * Get the list of inputs to be collected from the user when this action is sent. 982 * May return null if no remote inputs were added. 983 */ getRemoteInputs()984 public RemoteInput[] getRemoteInputs() { 985 return mRemoteInputs; 986 } 987 988 /** 989 * Builder class for {@link Action} objects. 990 */ 991 public static final class Builder { 992 private final Icon mIcon; 993 private final CharSequence mTitle; 994 private final PendingIntent mIntent; 995 private final Bundle mExtras; 996 private ArrayList<RemoteInput> mRemoteInputs; 997 998 /** 999 * Construct a new builder for {@link Action} object. 1000 * @param icon icon to show for this action 1001 * @param title the title of the action 1002 * @param intent the {@link PendingIntent} to fire when users trigger this action 1003 */ 1004 @Deprecated Builder(int icon, CharSequence title, PendingIntent intent)1005 public Builder(int icon, CharSequence title, PendingIntent intent) { 1006 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null); 1007 } 1008 1009 /** 1010 * Construct a new builder for {@link Action} object. 1011 * @param icon icon to show for this action 1012 * @param title the title of the action 1013 * @param intent the {@link PendingIntent} to fire when users trigger this action 1014 */ Builder(Icon icon, CharSequence title, PendingIntent intent)1015 public Builder(Icon icon, CharSequence title, PendingIntent intent) { 1016 this(icon, title, intent, new Bundle(), null); 1017 } 1018 1019 /** 1020 * Construct a new builder for {@link Action} object using the fields from an 1021 * {@link Action}. 1022 * @param action the action to read fields from. 1023 */ Builder(Action action)1024 public Builder(Action action) { 1025 this(action.getIcon(), action.title, action.actionIntent, new Bundle(action.mExtras), 1026 action.getRemoteInputs()); 1027 } 1028 Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, RemoteInput[] remoteInputs)1029 private Builder(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1030 RemoteInput[] remoteInputs) { 1031 mIcon = icon; 1032 mTitle = title; 1033 mIntent = intent; 1034 mExtras = extras; 1035 if (remoteInputs != null) { 1036 mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length); 1037 Collections.addAll(mRemoteInputs, remoteInputs); 1038 } 1039 } 1040 1041 /** 1042 * Merge additional metadata into this builder. 1043 * 1044 * <p>Values within the Bundle will replace existing extras values in this Builder. 1045 * 1046 * @see Notification.Action#extras 1047 */ addExtras(Bundle extras)1048 public Builder addExtras(Bundle extras) { 1049 if (extras != null) { 1050 mExtras.putAll(extras); 1051 } 1052 return this; 1053 } 1054 1055 /** 1056 * Get the metadata Bundle used by this Builder. 1057 * 1058 * <p>The returned Bundle is shared with this Builder. 1059 */ getExtras()1060 public Bundle getExtras() { 1061 return mExtras; 1062 } 1063 1064 /** 1065 * Add an input to be collected from the user when this action is sent. 1066 * Response values can be retrieved from the fired intent by using the 1067 * {@link RemoteInput#getResultsFromIntent} function. 1068 * @param remoteInput a {@link RemoteInput} to add to the action 1069 * @return this object for method chaining 1070 */ addRemoteInput(RemoteInput remoteInput)1071 public Builder addRemoteInput(RemoteInput remoteInput) { 1072 if (mRemoteInputs == null) { 1073 mRemoteInputs = new ArrayList<RemoteInput>(); 1074 } 1075 mRemoteInputs.add(remoteInput); 1076 return this; 1077 } 1078 1079 /** 1080 * Apply an extender to this action builder. Extenders may be used to add 1081 * metadata or change options on this builder. 1082 */ extend(Extender extender)1083 public Builder extend(Extender extender) { 1084 extender.extend(this); 1085 return this; 1086 } 1087 1088 /** 1089 * Combine all of the options that have been set and return a new {@link Action} 1090 * object. 1091 * @return the built action 1092 */ build()1093 public Action build() { 1094 RemoteInput[] remoteInputs = mRemoteInputs != null 1095 ? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null; 1096 return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs); 1097 } 1098 } 1099 1100 @Override clone()1101 public Action clone() { 1102 return new Action( 1103 getIcon(), 1104 title, 1105 actionIntent, // safe to alias 1106 new Bundle(mExtras), 1107 getRemoteInputs()); 1108 } 1109 @Override describeContents()1110 public int describeContents() { 1111 return 0; 1112 } 1113 @Override writeToParcel(Parcel out, int flags)1114 public void writeToParcel(Parcel out, int flags) { 1115 final Icon ic = getIcon(); 1116 if (ic != null) { 1117 out.writeInt(1); 1118 ic.writeToParcel(out, 0); 1119 } else { 1120 out.writeInt(0); 1121 } 1122 TextUtils.writeToParcel(title, out, flags); 1123 if (actionIntent != null) { 1124 out.writeInt(1); 1125 actionIntent.writeToParcel(out, flags); 1126 } else { 1127 out.writeInt(0); 1128 } 1129 out.writeBundle(mExtras); 1130 out.writeTypedArray(mRemoteInputs, flags); 1131 } 1132 public static final Parcelable.Creator<Action> CREATOR = 1133 new Parcelable.Creator<Action>() { 1134 public Action createFromParcel(Parcel in) { 1135 return new Action(in); 1136 } 1137 public Action[] newArray(int size) { 1138 return new Action[size]; 1139 } 1140 }; 1141 1142 /** 1143 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 1144 * metadata or change options on an action builder. 1145 */ 1146 public interface Extender { 1147 /** 1148 * Apply this extender to a notification action builder. 1149 * @param builder the builder to be modified. 1150 * @return the build object for chaining. 1151 */ extend(Builder builder)1152 public Builder extend(Builder builder); 1153 } 1154 1155 /** 1156 * Wearable extender for notification actions. To add extensions to an action, 1157 * create a new {@link android.app.Notification.Action.WearableExtender} object using 1158 * the {@code WearableExtender()} constructor and apply it to a 1159 * {@link android.app.Notification.Action.Builder} using 1160 * {@link android.app.Notification.Action.Builder#extend}. 1161 * 1162 * <pre class="prettyprint"> 1163 * Notification.Action action = new Notification.Action.Builder( 1164 * R.drawable.archive_all, "Archive all", actionIntent) 1165 * .extend(new Notification.Action.WearableExtender() 1166 * .setAvailableOffline(false)) 1167 * .build();</pre> 1168 */ 1169 public static final class WearableExtender implements Extender { 1170 /** Notification action extra which contains wearable extensions */ 1171 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 1172 1173 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 1174 private static final String KEY_FLAGS = "flags"; 1175 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 1176 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 1177 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 1178 1179 // Flags bitwise-ored to mFlags 1180 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 1181 1182 // Default value for flags integer 1183 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 1184 1185 private int mFlags = DEFAULT_FLAGS; 1186 1187 private CharSequence mInProgressLabel; 1188 private CharSequence mConfirmLabel; 1189 private CharSequence mCancelLabel; 1190 1191 /** 1192 * Create a {@link android.app.Notification.Action.WearableExtender} with default 1193 * options. 1194 */ WearableExtender()1195 public WearableExtender() { 1196 } 1197 1198 /** 1199 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 1200 * wearable options present in an existing notification action. 1201 * @param action the notification action to inspect. 1202 */ WearableExtender(Action action)1203 public WearableExtender(Action action) { 1204 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 1205 if (wearableBundle != null) { 1206 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 1207 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 1208 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 1209 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 1210 } 1211 } 1212 1213 /** 1214 * Apply wearable extensions to a notification action that is being built. This is 1215 * typically called by the {@link android.app.Notification.Action.Builder#extend} 1216 * method of {@link android.app.Notification.Action.Builder}. 1217 */ 1218 @Override extend(Action.Builder builder)1219 public Action.Builder extend(Action.Builder builder) { 1220 Bundle wearableBundle = new Bundle(); 1221 1222 if (mFlags != DEFAULT_FLAGS) { 1223 wearableBundle.putInt(KEY_FLAGS, mFlags); 1224 } 1225 if (mInProgressLabel != null) { 1226 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 1227 } 1228 if (mConfirmLabel != null) { 1229 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 1230 } 1231 if (mCancelLabel != null) { 1232 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 1233 } 1234 1235 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 1236 return builder; 1237 } 1238 1239 @Override clone()1240 public WearableExtender clone() { 1241 WearableExtender that = new WearableExtender(); 1242 that.mFlags = this.mFlags; 1243 that.mInProgressLabel = this.mInProgressLabel; 1244 that.mConfirmLabel = this.mConfirmLabel; 1245 that.mCancelLabel = this.mCancelLabel; 1246 return that; 1247 } 1248 1249 /** 1250 * Set whether this action is available when the wearable device is not connected to 1251 * a companion device. The user can still trigger this action when the wearable device is 1252 * offline, but a visual hint will indicate that the action may not be available. 1253 * Defaults to true. 1254 */ setAvailableOffline(boolean availableOffline)1255 public WearableExtender setAvailableOffline(boolean availableOffline) { 1256 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 1257 return this; 1258 } 1259 1260 /** 1261 * Get whether this action is available when the wearable device is not connected to 1262 * a companion device. The user can still trigger this action when the wearable device is 1263 * offline, but a visual hint will indicate that the action may not be available. 1264 * Defaults to true. 1265 */ isAvailableOffline()1266 public boolean isAvailableOffline() { 1267 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 1268 } 1269 setFlag(int mask, boolean value)1270 private void setFlag(int mask, boolean value) { 1271 if (value) { 1272 mFlags |= mask; 1273 } else { 1274 mFlags &= ~mask; 1275 } 1276 } 1277 1278 /** 1279 * Set a label to display while the wearable is preparing to automatically execute the 1280 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 1281 * 1282 * @param label the label to display while the action is being prepared to execute 1283 * @return this object for method chaining 1284 */ setInProgressLabel(CharSequence label)1285 public WearableExtender setInProgressLabel(CharSequence label) { 1286 mInProgressLabel = label; 1287 return this; 1288 } 1289 1290 /** 1291 * Get the label to display while the wearable is preparing to automatically execute 1292 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 1293 * 1294 * @return the label to display while the action is being prepared to execute 1295 */ getInProgressLabel()1296 public CharSequence getInProgressLabel() { 1297 return mInProgressLabel; 1298 } 1299 1300 /** 1301 * Set a label to display to confirm that the action should be executed. 1302 * This is usually an imperative verb like "Send". 1303 * 1304 * @param label the label to confirm the action should be executed 1305 * @return this object for method chaining 1306 */ setConfirmLabel(CharSequence label)1307 public WearableExtender setConfirmLabel(CharSequence label) { 1308 mConfirmLabel = label; 1309 return this; 1310 } 1311 1312 /** 1313 * Get the label to display to confirm that the action should be executed. 1314 * This is usually an imperative verb like "Send". 1315 * 1316 * @return the label to confirm the action should be executed 1317 */ getConfirmLabel()1318 public CharSequence getConfirmLabel() { 1319 return mConfirmLabel; 1320 } 1321 1322 /** 1323 * Set a label to display to cancel the action. 1324 * This is usually an imperative verb, like "Cancel". 1325 * 1326 * @param label the label to display to cancel the action 1327 * @return this object for method chaining 1328 */ setCancelLabel(CharSequence label)1329 public WearableExtender setCancelLabel(CharSequence label) { 1330 mCancelLabel = label; 1331 return this; 1332 } 1333 1334 /** 1335 * Get the label to display to cancel the action. 1336 * This is usually an imperative verb like "Cancel". 1337 * 1338 * @return the label to display to cancel the action 1339 */ getCancelLabel()1340 public CharSequence getCancelLabel() { 1341 return mCancelLabel; 1342 } 1343 } 1344 } 1345 1346 /** 1347 * Array of all {@link Action} structures attached to this notification by 1348 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 1349 * {@link android.service.notification.NotificationListenerService} that provide an alternative 1350 * interface for invoking actions. 1351 */ 1352 public Action[] actions; 1353 1354 /** 1355 * Replacement version of this notification whose content will be shown 1356 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 1357 * and {@link #VISIBILITY_PUBLIC}. 1358 */ 1359 public Notification publicVersion; 1360 1361 /** 1362 * Constructs a Notification object with default values. 1363 * You might want to consider using {@link Builder} instead. 1364 */ Notification()1365 public Notification() 1366 { 1367 this.when = System.currentTimeMillis(); 1368 this.priority = PRIORITY_DEFAULT; 1369 } 1370 1371 /** 1372 * @hide 1373 */ Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)1374 public Notification(Context context, int icon, CharSequence tickerText, long when, 1375 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 1376 { 1377 new Builder(context) 1378 .setWhen(when) 1379 .setSmallIcon(icon) 1380 .setTicker(tickerText) 1381 .setContentTitle(contentTitle) 1382 .setContentText(contentText) 1383 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) 1384 .buildInto(this); 1385 } 1386 1387 /** 1388 * Constructs a Notification object with the information needed to 1389 * have a status bar icon without the standard expanded view. 1390 * 1391 * @param icon The resource id of the icon to put in the status bar. 1392 * @param tickerText The text that flows by in the status bar when the notification first 1393 * activates. 1394 * @param when The time to show in the time field. In the System.currentTimeMillis 1395 * timebase. 1396 * 1397 * @deprecated Use {@link Builder} instead. 1398 */ 1399 @Deprecated Notification(int icon, CharSequence tickerText, long when)1400 public Notification(int icon, CharSequence tickerText, long when) 1401 { 1402 this.icon = icon; 1403 this.tickerText = tickerText; 1404 this.when = when; 1405 } 1406 1407 /** 1408 * Unflatten the notification from a parcel. 1409 */ Notification(Parcel parcel)1410 public Notification(Parcel parcel) 1411 { 1412 int version = parcel.readInt(); 1413 1414 when = parcel.readLong(); 1415 if (parcel.readInt() != 0) { 1416 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 1417 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 1418 icon = mSmallIcon.getResId(); 1419 } 1420 } 1421 number = parcel.readInt(); 1422 if (parcel.readInt() != 0) { 1423 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 1424 } 1425 if (parcel.readInt() != 0) { 1426 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 1427 } 1428 if (parcel.readInt() != 0) { 1429 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 1430 } 1431 if (parcel.readInt() != 0) { 1432 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 1433 } 1434 if (parcel.readInt() != 0) { 1435 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 1436 } 1437 if (parcel.readInt() != 0) { 1438 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 1439 } 1440 defaults = parcel.readInt(); 1441 flags = parcel.readInt(); 1442 if (parcel.readInt() != 0) { 1443 sound = Uri.CREATOR.createFromParcel(parcel); 1444 } 1445 1446 audioStreamType = parcel.readInt(); 1447 if (parcel.readInt() != 0) { 1448 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 1449 } 1450 vibrate = parcel.createLongArray(); 1451 ledARGB = parcel.readInt(); 1452 ledOnMS = parcel.readInt(); 1453 ledOffMS = parcel.readInt(); 1454 iconLevel = parcel.readInt(); 1455 1456 if (parcel.readInt() != 0) { 1457 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 1458 } 1459 1460 priority = parcel.readInt(); 1461 1462 category = parcel.readString(); 1463 1464 mGroupKey = parcel.readString(); 1465 1466 mSortKey = parcel.readString(); 1467 1468 extras = parcel.readBundle(); // may be null 1469 1470 actions = parcel.createTypedArray(Action.CREATOR); // may be null 1471 1472 if (parcel.readInt() != 0) { 1473 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 1474 } 1475 1476 if (parcel.readInt() != 0) { 1477 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 1478 } 1479 1480 visibility = parcel.readInt(); 1481 1482 if (parcel.readInt() != 0) { 1483 publicVersion = Notification.CREATOR.createFromParcel(parcel); 1484 } 1485 1486 color = parcel.readInt(); 1487 } 1488 1489 @Override clone()1490 public Notification clone() { 1491 Notification that = new Notification(); 1492 cloneInto(that, true); 1493 return that; 1494 } 1495 1496 /** 1497 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 1498 * of this into that. 1499 * @hide 1500 */ cloneInto(Notification that, boolean heavy)1501 public void cloneInto(Notification that, boolean heavy) { 1502 that.when = this.when; 1503 that.mSmallIcon = this.mSmallIcon; 1504 that.number = this.number; 1505 1506 // PendingIntents are global, so there's no reason (or way) to clone them. 1507 that.contentIntent = this.contentIntent; 1508 that.deleteIntent = this.deleteIntent; 1509 that.fullScreenIntent = this.fullScreenIntent; 1510 1511 if (this.tickerText != null) { 1512 that.tickerText = this.tickerText.toString(); 1513 } 1514 if (heavy && this.tickerView != null) { 1515 that.tickerView = this.tickerView.clone(); 1516 } 1517 if (heavy && this.contentView != null) { 1518 that.contentView = this.contentView.clone(); 1519 } 1520 if (heavy && this.mLargeIcon != null) { 1521 that.mLargeIcon = this.mLargeIcon; 1522 } 1523 that.iconLevel = this.iconLevel; 1524 that.sound = this.sound; // android.net.Uri is immutable 1525 that.audioStreamType = this.audioStreamType; 1526 if (this.audioAttributes != null) { 1527 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 1528 } 1529 1530 final long[] vibrate = this.vibrate; 1531 if (vibrate != null) { 1532 final int N = vibrate.length; 1533 final long[] vib = that.vibrate = new long[N]; 1534 System.arraycopy(vibrate, 0, vib, 0, N); 1535 } 1536 1537 that.ledARGB = this.ledARGB; 1538 that.ledOnMS = this.ledOnMS; 1539 that.ledOffMS = this.ledOffMS; 1540 that.defaults = this.defaults; 1541 1542 that.flags = this.flags; 1543 1544 that.priority = this.priority; 1545 1546 that.category = this.category; 1547 1548 that.mGroupKey = this.mGroupKey; 1549 1550 that.mSortKey = this.mSortKey; 1551 1552 if (this.extras != null) { 1553 try { 1554 that.extras = new Bundle(this.extras); 1555 // will unparcel 1556 that.extras.size(); 1557 } catch (BadParcelableException e) { 1558 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 1559 that.extras = null; 1560 } 1561 } 1562 1563 if (this.actions != null) { 1564 that.actions = new Action[this.actions.length]; 1565 for(int i=0; i<this.actions.length; i++) { 1566 that.actions[i] = this.actions[i].clone(); 1567 } 1568 } 1569 1570 if (heavy && this.bigContentView != null) { 1571 that.bigContentView = this.bigContentView.clone(); 1572 } 1573 1574 if (heavy && this.headsUpContentView != null) { 1575 that.headsUpContentView = this.headsUpContentView.clone(); 1576 } 1577 1578 that.visibility = this.visibility; 1579 1580 if (this.publicVersion != null) { 1581 that.publicVersion = new Notification(); 1582 this.publicVersion.cloneInto(that.publicVersion, heavy); 1583 } 1584 1585 that.color = this.color; 1586 1587 if (!heavy) { 1588 that.lightenPayload(); // will clean out extras 1589 } 1590 } 1591 1592 /** 1593 * Removes heavyweight parts of the Notification object for archival or for sending to 1594 * listeners when the full contents are not necessary. 1595 * @hide 1596 */ lightenPayload()1597 public final void lightenPayload() { 1598 tickerView = null; 1599 contentView = null; 1600 bigContentView = null; 1601 headsUpContentView = null; 1602 mLargeIcon = null; 1603 if (extras != null) { 1604 extras.remove(Notification.EXTRA_LARGE_ICON); 1605 extras.remove(Notification.EXTRA_LARGE_ICON_BIG); 1606 extras.remove(Notification.EXTRA_PICTURE); 1607 extras.remove(Notification.EXTRA_BIG_TEXT); 1608 // Prevent light notifications from being rebuilt. 1609 extras.remove(Builder.EXTRA_NEEDS_REBUILD); 1610 } 1611 } 1612 1613 /** 1614 * Make sure this CharSequence is safe to put into a bundle, which basically 1615 * means it had better not be some custom Parcelable implementation. 1616 * @hide 1617 */ safeCharSequence(CharSequence cs)1618 public static CharSequence safeCharSequence(CharSequence cs) { 1619 if (cs == null) return cs; 1620 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 1621 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 1622 } 1623 if (cs instanceof Parcelable) { 1624 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 1625 + " instance is a custom Parcelable and not allowed in Notification"); 1626 return cs.toString(); 1627 } 1628 1629 return cs; 1630 } 1631 describeContents()1632 public int describeContents() { 1633 return 0; 1634 } 1635 1636 /** 1637 * Flatten this notification into a parcel. 1638 */ writeToParcel(Parcel parcel, int flags)1639 public void writeToParcel(Parcel parcel, int flags) 1640 { 1641 parcel.writeInt(1); 1642 1643 parcel.writeLong(when); 1644 if (mSmallIcon == null && icon != 0) { 1645 // you snuck an icon in here without using the builder; let's try to keep it 1646 mSmallIcon = Icon.createWithResource("", icon); 1647 } 1648 if (mSmallIcon != null) { 1649 parcel.writeInt(1); 1650 mSmallIcon.writeToParcel(parcel, 0); 1651 } else { 1652 parcel.writeInt(0); 1653 } 1654 parcel.writeInt(number); 1655 if (contentIntent != null) { 1656 parcel.writeInt(1); 1657 contentIntent.writeToParcel(parcel, 0); 1658 } else { 1659 parcel.writeInt(0); 1660 } 1661 if (deleteIntent != null) { 1662 parcel.writeInt(1); 1663 deleteIntent.writeToParcel(parcel, 0); 1664 } else { 1665 parcel.writeInt(0); 1666 } 1667 if (tickerText != null) { 1668 parcel.writeInt(1); 1669 TextUtils.writeToParcel(tickerText, parcel, flags); 1670 } else { 1671 parcel.writeInt(0); 1672 } 1673 if (tickerView != null) { 1674 parcel.writeInt(1); 1675 tickerView.writeToParcel(parcel, 0); 1676 } else { 1677 parcel.writeInt(0); 1678 } 1679 if (contentView != null) { 1680 parcel.writeInt(1); 1681 contentView.writeToParcel(parcel, 0); 1682 } else { 1683 parcel.writeInt(0); 1684 } 1685 if (mLargeIcon != null) { 1686 parcel.writeInt(1); 1687 mLargeIcon.writeToParcel(parcel, 0); 1688 } else { 1689 parcel.writeInt(0); 1690 } 1691 1692 parcel.writeInt(defaults); 1693 parcel.writeInt(this.flags); 1694 1695 if (sound != null) { 1696 parcel.writeInt(1); 1697 sound.writeToParcel(parcel, 0); 1698 } else { 1699 parcel.writeInt(0); 1700 } 1701 parcel.writeInt(audioStreamType); 1702 1703 if (audioAttributes != null) { 1704 parcel.writeInt(1); 1705 audioAttributes.writeToParcel(parcel, 0); 1706 } else { 1707 parcel.writeInt(0); 1708 } 1709 1710 parcel.writeLongArray(vibrate); 1711 parcel.writeInt(ledARGB); 1712 parcel.writeInt(ledOnMS); 1713 parcel.writeInt(ledOffMS); 1714 parcel.writeInt(iconLevel); 1715 1716 if (fullScreenIntent != null) { 1717 parcel.writeInt(1); 1718 fullScreenIntent.writeToParcel(parcel, 0); 1719 } else { 1720 parcel.writeInt(0); 1721 } 1722 1723 parcel.writeInt(priority); 1724 1725 parcel.writeString(category); 1726 1727 parcel.writeString(mGroupKey); 1728 1729 parcel.writeString(mSortKey); 1730 1731 parcel.writeBundle(extras); // null ok 1732 1733 parcel.writeTypedArray(actions, 0); // null ok 1734 1735 if (bigContentView != null) { 1736 parcel.writeInt(1); 1737 bigContentView.writeToParcel(parcel, 0); 1738 } else { 1739 parcel.writeInt(0); 1740 } 1741 1742 if (headsUpContentView != null) { 1743 parcel.writeInt(1); 1744 headsUpContentView.writeToParcel(parcel, 0); 1745 } else { 1746 parcel.writeInt(0); 1747 } 1748 1749 parcel.writeInt(visibility); 1750 1751 if (publicVersion != null) { 1752 parcel.writeInt(1); 1753 publicVersion.writeToParcel(parcel, 0); 1754 } else { 1755 parcel.writeInt(0); 1756 } 1757 1758 parcel.writeInt(color); 1759 } 1760 1761 /** 1762 * Parcelable.Creator that instantiates Notification objects 1763 */ 1764 public static final Parcelable.Creator<Notification> CREATOR 1765 = new Parcelable.Creator<Notification>() 1766 { 1767 public Notification createFromParcel(Parcel parcel) 1768 { 1769 return new Notification(parcel); 1770 } 1771 1772 public Notification[] newArray(int size) 1773 { 1774 return new Notification[size]; 1775 } 1776 }; 1777 1778 /** 1779 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 1780 * layout. 1781 * 1782 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 1783 * in the view.</p> 1784 * @param context The context for your application / activity. 1785 * @param contentTitle The title that goes in the expanded entry. 1786 * @param contentText The text that goes in the expanded entry. 1787 * @param contentIntent The intent to launch when the user clicks the expanded notification. 1788 * If this is an activity, it must include the 1789 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 1790 * that you take care of task management as described in the 1791 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 1792 * Stack</a> document. 1793 * 1794 * @deprecated Use {@link Builder} instead. 1795 * @removed 1796 */ 1797 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)1798 public void setLatestEventInfo(Context context, 1799 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 1800 Notification.Builder builder = new Notification.Builder(context); 1801 1802 // First, ensure that key pieces of information that may have been set directly 1803 // are preserved 1804 builder.setWhen(this.when); 1805 builder.setSmallIcon(this.icon); 1806 builder.setPriority(this.priority); 1807 builder.setTicker(this.tickerText); 1808 builder.setNumber(this.number); 1809 builder.setColor(this.color); 1810 builder.mFlags = this.flags; 1811 builder.setSound(this.sound, this.audioStreamType); 1812 builder.setDefaults(this.defaults); 1813 builder.setVibrate(this.vibrate); 1814 builder.setDeleteIntent(this.deleteIntent); 1815 1816 // now apply the latestEventInfo fields 1817 if (contentTitle != null) { 1818 builder.setContentTitle(contentTitle); 1819 } 1820 if (contentText != null) { 1821 builder.setContentText(contentText); 1822 } 1823 builder.setContentIntent(contentIntent); 1824 builder.buildInto(this); 1825 } 1826 1827 @Override toString()1828 public String toString() { 1829 StringBuilder sb = new StringBuilder(); 1830 sb.append("Notification(pri="); 1831 sb.append(priority); 1832 sb.append(" contentView="); 1833 if (contentView != null) { 1834 sb.append(contentView.getPackage()); 1835 sb.append("/0x"); 1836 sb.append(Integer.toHexString(contentView.getLayoutId())); 1837 } else { 1838 sb.append("null"); 1839 } 1840 sb.append(" vibrate="); 1841 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 1842 sb.append("default"); 1843 } else if (this.vibrate != null) { 1844 int N = this.vibrate.length-1; 1845 sb.append("["); 1846 for (int i=0; i<N; i++) { 1847 sb.append(this.vibrate[i]); 1848 sb.append(','); 1849 } 1850 if (N != -1) { 1851 sb.append(this.vibrate[N]); 1852 } 1853 sb.append("]"); 1854 } else { 1855 sb.append("null"); 1856 } 1857 sb.append(" sound="); 1858 if ((this.defaults & DEFAULT_SOUND) != 0) { 1859 sb.append("default"); 1860 } else if (this.sound != null) { 1861 sb.append(this.sound.toString()); 1862 } else { 1863 sb.append("null"); 1864 } 1865 if (this.tickerText != null) { 1866 sb.append(" tick"); 1867 } 1868 sb.append(" defaults=0x"); 1869 sb.append(Integer.toHexString(this.defaults)); 1870 sb.append(" flags=0x"); 1871 sb.append(Integer.toHexString(this.flags)); 1872 sb.append(String.format(" color=0x%08x", this.color)); 1873 if (this.category != null) { 1874 sb.append(" category="); 1875 sb.append(this.category); 1876 } 1877 if (this.mGroupKey != null) { 1878 sb.append(" groupKey="); 1879 sb.append(this.mGroupKey); 1880 } 1881 if (this.mSortKey != null) { 1882 sb.append(" sortKey="); 1883 sb.append(this.mSortKey); 1884 } 1885 if (actions != null) { 1886 sb.append(" actions="); 1887 sb.append(actions.length); 1888 } 1889 sb.append(" vis="); 1890 sb.append(visibilityToString(this.visibility)); 1891 if (this.publicVersion != null) { 1892 sb.append(" publicVersion="); 1893 sb.append(publicVersion.toString()); 1894 } 1895 sb.append(")"); 1896 return sb.toString(); 1897 } 1898 1899 /** 1900 * {@hide} 1901 */ visibilityToString(int vis)1902 public static String visibilityToString(int vis) { 1903 switch (vis) { 1904 case VISIBILITY_PRIVATE: 1905 return "PRIVATE"; 1906 case VISIBILITY_PUBLIC: 1907 return "PUBLIC"; 1908 case VISIBILITY_SECRET: 1909 return "SECRET"; 1910 default: 1911 return "UNKNOWN(" + String.valueOf(vis) + ")"; 1912 } 1913 } 1914 1915 /** 1916 * {@hide} 1917 */ priorityToString(@riority int pri)1918 public static String priorityToString(@Priority int pri) { 1919 switch (pri) { 1920 case PRIORITY_MIN: 1921 return "MIN"; 1922 case PRIORITY_LOW: 1923 return "LOW"; 1924 case PRIORITY_DEFAULT: 1925 return "DEFAULT"; 1926 case PRIORITY_HIGH: 1927 return "HIGH"; 1928 case PRIORITY_MAX: 1929 return "MAX"; 1930 default: 1931 return "UNKNOWN(" + String.valueOf(pri) + ")"; 1932 } 1933 } 1934 1935 /** 1936 * The small icon representing this notification in the status bar and content view. 1937 * 1938 * @return the small icon representing this notification. 1939 * 1940 * @see Builder#getSmallIcon() 1941 * @see Builder#setSmallIcon(Icon) 1942 */ getSmallIcon()1943 public Icon getSmallIcon() { 1944 return mSmallIcon; 1945 } 1946 1947 /** 1948 * Used when notifying to clean up legacy small icons. 1949 * @hide 1950 */ setSmallIcon(Icon icon)1951 public void setSmallIcon(Icon icon) { 1952 mSmallIcon = icon; 1953 } 1954 1955 /** 1956 * The large icon shown in this notification's content view. 1957 * @see Builder#getLargeIcon() 1958 * @see Builder#setLargeIcon(Icon) 1959 */ getLargeIcon()1960 public Icon getLargeIcon() { 1961 return mLargeIcon; 1962 } 1963 1964 /** 1965 * @hide 1966 */ isValid()1967 public boolean isValid() { 1968 // Would like to check for icon!=0 here, too, but NotificationManagerService accepts that 1969 // for legacy reasons. 1970 return contentView != null || extras.getBoolean(Builder.EXTRA_REBUILD_CONTENT_VIEW); 1971 } 1972 1973 /** 1974 * @hide 1975 */ isGroupSummary()1976 public boolean isGroupSummary() { 1977 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 1978 } 1979 1980 /** 1981 * @hide 1982 */ isGroupChild()1983 public boolean isGroupChild() { 1984 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 1985 } 1986 1987 /** 1988 * Builder class for {@link Notification} objects. 1989 * 1990 * Provides a convenient way to set the various fields of a {@link Notification} and generate 1991 * content views using the platform's notification layout template. If your app supports 1992 * versions of Android as old as API level 4, you can instead use 1993 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 1994 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 1995 * library</a>. 1996 * 1997 * <p>Example: 1998 * 1999 * <pre class="prettyprint"> 2000 * Notification noti = new Notification.Builder(mContext) 2001 * .setContentTitle("New mail from " + sender.toString()) 2002 * .setContentText(subject) 2003 * .setSmallIcon(R.drawable.new_mail) 2004 * .setLargeIcon(aBitmap) 2005 * .build(); 2006 * </pre> 2007 */ 2008 public static class Builder { 2009 private static final int MAX_ACTION_BUTTONS = 3; 2010 private static final float LARGE_TEXT_SCALE = 1.3f; 2011 2012 /** 2013 * @hide 2014 */ 2015 public static final String EXTRA_NEEDS_REBUILD = "android.rebuild"; 2016 2017 /** 2018 * @hide 2019 */ 2020 public static final String EXTRA_REBUILD_LARGE_ICON = "android.rebuild.largeIcon"; 2021 /** 2022 * @hide 2023 */ 2024 public static final String EXTRA_REBUILD_CONTENT_VIEW = "android.rebuild.contentView"; 2025 /** 2026 * @hide 2027 */ 2028 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 2029 "android.rebuild.contentViewActionCount"; 2030 /** 2031 * @hide 2032 */ 2033 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW 2034 = "android.rebuild.bigView"; 2035 /** 2036 * @hide 2037 */ 2038 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 2039 = "android.rebuild.bigViewActionCount"; 2040 /** 2041 * @hide 2042 */ 2043 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW 2044 = "android.rebuild.hudView"; 2045 /** 2046 * @hide 2047 */ 2048 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 2049 = "android.rebuild.hudViewActionCount"; 2050 2051 /** 2052 * The ApplicationInfo of the package that created the notification, used to create 2053 * a context to rebuild the notification via a Builder. 2054 * @hide 2055 */ 2056 private static final String EXTRA_REBUILD_CONTEXT_APPLICATION_INFO = 2057 "android.rebuild.applicationInfo"; 2058 2059 // Whether to enable stripping (at post time) & rebuilding (at listener receive time) of 2060 // memory intensive resources. 2061 private static final boolean STRIP_AND_REBUILD = true; 2062 2063 private Context mContext; 2064 2065 private long mWhen; 2066 private Icon mSmallIcon, mLargeIcon; 2067 private int mSmallIconLevel; 2068 private int mNumber; 2069 private CharSequence mContentTitle; 2070 private CharSequence mContentText; 2071 private CharSequence mContentInfo; 2072 private CharSequence mSubText; 2073 private PendingIntent mContentIntent; 2074 private RemoteViews mContentView; 2075 private PendingIntent mDeleteIntent; 2076 private PendingIntent mFullScreenIntent; 2077 private CharSequence mTickerText; 2078 private RemoteViews mTickerView; 2079 private Uri mSound; 2080 private int mAudioStreamType; 2081 private AudioAttributes mAudioAttributes; 2082 private long[] mVibrate; 2083 private int mLedArgb; 2084 private int mLedOnMs; 2085 private int mLedOffMs; 2086 private int mDefaults; 2087 private int mFlags; 2088 private int mProgressMax; 2089 private int mProgress; 2090 private boolean mProgressIndeterminate; 2091 private String mCategory; 2092 private String mGroupKey; 2093 private String mSortKey; 2094 private Bundle mExtras; 2095 private int mPriority; 2096 private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); 2097 private boolean mUseChronometer; 2098 private Style mStyle; 2099 private boolean mShowWhen = true; 2100 private int mVisibility = VISIBILITY_PRIVATE; 2101 private Notification mPublicVersion = null; 2102 private final NotificationColorUtil mColorUtil; 2103 private ArrayList<String> mPeople; 2104 private int mColor = COLOR_DEFAULT; 2105 2106 /** 2107 * The user that built the notification originally. 2108 */ 2109 private int mOriginatingUserId; 2110 2111 /** 2112 * Contains extras related to rebuilding during the build phase. 2113 */ 2114 private Bundle mRebuildBundle = new Bundle(); 2115 /** 2116 * Contains the notification to rebuild when this Builder is in "rebuild" mode. 2117 * Null otherwise. 2118 */ 2119 private Notification mRebuildNotification = null; 2120 2121 /** 2122 * Whether the build notification has three lines. This is used to make the top padding for 2123 * both the contracted and expanded layout consistent. 2124 * 2125 * <p> 2126 * This field is only valid during the build phase. 2127 */ 2128 private boolean mHasThreeLines; 2129 2130 /** 2131 * Constructs a new Builder with the defaults: 2132 * 2133 2134 * <table> 2135 * <tr><th align=right>priority</th> 2136 * <td>{@link #PRIORITY_DEFAULT}</td></tr> 2137 * <tr><th align=right>when</th> 2138 * <td>now ({@link System#currentTimeMillis()})</td></tr> 2139 * <tr><th align=right>audio stream</th> 2140 * <td>{@link #STREAM_DEFAULT}</td></tr> 2141 * </table> 2142 * 2143 2144 * @param context 2145 * A {@link Context} that will be used by the Builder to construct the 2146 * RemoteViews. The Context will not be held past the lifetime of this Builder 2147 * object. 2148 */ Builder(Context context)2149 public Builder(Context context) { 2150 /* 2151 * Important compatibility note! 2152 * Some apps out in the wild create a Notification.Builder in their Activity subclass 2153 * constructor for later use. At this point Activities - themselves subclasses of 2154 * ContextWrapper - do not have their inner Context populated yet. This means that 2155 * any calls to Context methods from within this constructor can cause NPEs in existing 2156 * apps. Any data populated from mContext should therefore be populated lazily to 2157 * preserve compatibility. 2158 */ 2159 mContext = context; 2160 2161 // Set defaults to match the defaults of a Notification 2162 mWhen = System.currentTimeMillis(); 2163 mAudioStreamType = STREAM_DEFAULT; 2164 mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 2165 mPriority = PRIORITY_DEFAULT; 2166 mPeople = new ArrayList<String>(); 2167 2168 mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.LOLLIPOP ? 2169 NotificationColorUtil.getInstance(mContext) : null; 2170 } 2171 2172 /** 2173 * Creates a Builder for rebuilding the given Notification. 2174 * <p> 2175 * Call {@link #rebuild()} to retrieve the rebuilt version of 'n'. 2176 */ 2177 private Builder(Context context, Notification n) { 2178 this(context); 2179 mRebuildNotification = n; 2180 restoreFromNotification(n); 2181 2182 Style style = null; 2183 Bundle extras = n.extras; 2184 String templateClass = extras.getString(EXTRA_TEMPLATE); 2185 if (!TextUtils.isEmpty(templateClass)) { 2186 Class<? extends Style> styleClass = getNotificationStyleClass(templateClass); 2187 if (styleClass == null) { 2188 Log.d(TAG, "Unknown style class: " + styleClass); 2189 return; 2190 } 2191 2192 try { 2193 Constructor<? extends Style> constructor = styleClass.getConstructor(); 2194 constructor.setAccessible(true); 2195 style = constructor.newInstance(); 2196 style.restoreFromExtras(extras); 2197 } catch (Throwable t) { 2198 Log.e(TAG, "Could not create Style", t); 2199 return; 2200 } 2201 } 2202 if (style != null) { 2203 setStyle(style); 2204 } 2205 } 2206 2207 /** 2208 * Add a timestamp pertaining to the notification (usually the time the event occurred). 2209 * It will be shown in the notification content view by default; use 2210 * {@link #setShowWhen(boolean) setShowWhen} to control this. 2211 * 2212 * @see Notification#when 2213 */ 2214 public Builder setWhen(long when) { 2215 mWhen = when; 2216 return this; 2217 } 2218 2219 /** 2220 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 2221 * in the content view. 2222 */ 2223 public Builder setShowWhen(boolean show) { 2224 mShowWhen = show; 2225 return this; 2226 } 2227 2228 /** 2229 * Show the {@link Notification#when} field as a stopwatch. 2230 * 2231 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 2232 * automatically updating display of the minutes and seconds since <code>when</code>. 2233 * 2234 * Useful when showing an elapsed time (like an ongoing phone call). 2235 * 2236 * @see android.widget.Chronometer 2237 * @see Notification#when 2238 */ 2239 public Builder setUsesChronometer(boolean b) { 2240 mUseChronometer = b; 2241 return this; 2242 } 2243 2244 /** 2245 * Set the small icon resource, which will be used to represent the notification in the 2246 * status bar. 2247 * 2248 2249 * The platform template for the expanded view will draw this icon in the left, unless a 2250 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 2251 * icon will be moved to the right-hand side. 2252 * 2253 2254 * @param icon 2255 * A resource ID in the application's package of the drawable to use. 2256 * @see Notification#icon 2257 */ 2258 public Builder setSmallIcon(@DrawableRes int icon) { 2259 return setSmallIcon(icon != 0 2260 ? Icon.createWithResource(mContext, icon) 2261 : null); 2262 } 2263 2264 /** 2265 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 2266 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 2267 * LevelListDrawable}. 2268 * 2269 * @param icon A resource ID in the application's package of the drawable to use. 2270 * @param level The level to use for the icon. 2271 * 2272 * @see Notification#icon 2273 * @see Notification#iconLevel 2274 */ 2275 public Builder setSmallIcon(@DrawableRes int icon, int level) { 2276 mSmallIconLevel = level; 2277 return setSmallIcon(icon); 2278 } 2279 2280 /** 2281 * Set the small icon, which will be used to represent the notification in the 2282 * status bar and content view (unless overriden there by a 2283 * {@link #setLargeIcon(Bitmap) large icon}). 2284 * 2285 * @param icon An Icon object to use. 2286 * @see Notification#icon 2287 */ 2288 public Builder setSmallIcon(Icon icon) { 2289 mSmallIcon = icon; 2290 return this; 2291 } 2292 2293 /** 2294 * Set the first line of text in the platform notification template. 2295 */ 2296 public Builder setContentTitle(CharSequence title) { 2297 mContentTitle = safeCharSequence(title); 2298 return this; 2299 } 2300 2301 /** 2302 * Set the second line of text in the platform notification template. 2303 */ 2304 public Builder setContentText(CharSequence text) { 2305 mContentText = safeCharSequence(text); 2306 return this; 2307 } 2308 2309 /** 2310 * Set the third line of text in the platform notification template. 2311 * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the 2312 * same location in the standard template. 2313 */ 2314 public Builder setSubText(CharSequence text) { 2315 mSubText = safeCharSequence(text); 2316 return this; 2317 } 2318 2319 /** 2320 * Set the large number at the right-hand side of the notification. This is 2321 * equivalent to setContentInfo, although it might show the number in a different 2322 * font size for readability. 2323 */ 2324 public Builder setNumber(int number) { 2325 mNumber = number; 2326 return this; 2327 } 2328 2329 /** 2330 * A small piece of additional information pertaining to this notification. 2331 * 2332 * The platform template will draw this on the last line of the notification, at the far 2333 * right (to the right of a smallIcon if it has been placed there). 2334 */ 2335 public Builder setContentInfo(CharSequence info) { 2336 mContentInfo = safeCharSequence(info); 2337 return this; 2338 } 2339 2340 /** 2341 * Set the progress this notification represents. 2342 * 2343 * The platform template will represent this using a {@link ProgressBar}. 2344 */ 2345 public Builder setProgress(int max, int progress, boolean indeterminate) { 2346 mProgressMax = max; 2347 mProgress = progress; 2348 mProgressIndeterminate = indeterminate; 2349 return this; 2350 } 2351 2352 /** 2353 * Supply a custom RemoteViews to use instead of the platform template. 2354 * 2355 * @see Notification#contentView 2356 */ 2357 public Builder setContent(RemoteViews views) { 2358 mContentView = views; 2359 return this; 2360 } 2361 2362 /** 2363 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 2364 * 2365 * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 2366 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 2367 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 2368 * to assign PendingIntents to individual views in that custom layout (i.e., to create 2369 * clickable buttons inside the notification view). 2370 * 2371 * @see Notification#contentIntent Notification.contentIntent 2372 */ 2373 public Builder setContentIntent(PendingIntent intent) { 2374 mContentIntent = intent; 2375 return this; 2376 } 2377 2378 /** 2379 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 2380 * 2381 * @see Notification#deleteIntent 2382 */ 2383 public Builder setDeleteIntent(PendingIntent intent) { 2384 mDeleteIntent = intent; 2385 return this; 2386 } 2387 2388 /** 2389 * An intent to launch instead of posting the notification to the status bar. 2390 * Only for use with extremely high-priority notifications demanding the user's 2391 * <strong>immediate</strong> attention, such as an incoming phone call or 2392 * alarm clock that the user has explicitly set to a particular time. 2393 * If this facility is used for something else, please give the user an option 2394 * to turn it off and use a normal notification, as this can be extremely 2395 * disruptive. 2396 * 2397 * <p> 2398 * The system UI may choose to display a heads-up notification, instead of 2399 * launching this intent, while the user is using the device. 2400 * </p> 2401 * 2402 * @param intent The pending intent to launch. 2403 * @param highPriority Passing true will cause this notification to be sent 2404 * even if other notifications are suppressed. 2405 * 2406 * @see Notification#fullScreenIntent 2407 */ 2408 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 2409 mFullScreenIntent = intent; 2410 setFlag(FLAG_HIGH_PRIORITY, highPriority); 2411 return this; 2412 } 2413 2414 /** 2415 * Set the "ticker" text which is sent to accessibility services. 2416 * 2417 * @see Notification#tickerText 2418 */ 2419 public Builder setTicker(CharSequence tickerText) { 2420 mTickerText = safeCharSequence(tickerText); 2421 return this; 2422 } 2423 2424 /** 2425 * Obsolete version of {@link #setTicker(CharSequence)}. 2426 * 2427 */ 2428 @Deprecated 2429 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 2430 mTickerText = safeCharSequence(tickerText); 2431 mTickerView = views; // we'll save it for you anyway 2432 return this; 2433 } 2434 2435 /** 2436 * Add a large icon to the notification content view. 2437 * 2438 * In the platform template, this image will be shown on the left of the notification view 2439 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 2440 * badge atop the large icon). 2441 */ 2442 public Builder setLargeIcon(Bitmap b) { 2443 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 2444 } 2445 2446 /** 2447 * Add a large icon to the notification content view. 2448 * 2449 * In the platform template, this image will be shown on the left of the notification view 2450 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 2451 * badge atop the large icon). 2452 */ 2453 public Builder setLargeIcon(Icon icon) { 2454 mLargeIcon = icon; 2455 return this; 2456 } 2457 2458 /** 2459 * Set the sound to play. 2460 * 2461 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 2462 * for notifications. 2463 * 2464 * <p> 2465 * A notification that is noisy is more likely to be presented as a heads-up notification. 2466 * </p> 2467 * 2468 * @see Notification#sound 2469 */ 2470 public Builder setSound(Uri sound) { 2471 mSound = sound; 2472 mAudioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 2473 return this; 2474 } 2475 2476 /** 2477 * Set the sound to play, along with a specific stream on which to play it. 2478 * 2479 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 2480 * 2481 * <p> 2482 * A notification that is noisy is more likely to be presented as a heads-up notification. 2483 * </p> 2484 * @deprecated use {@link #setSound(Uri, AudioAttributes)} instead. 2485 * @see Notification#sound 2486 */ 2487 @Deprecated 2488 public Builder setSound(Uri sound, int streamType) { 2489 mSound = sound; 2490 mAudioStreamType = streamType; 2491 return this; 2492 } 2493 2494 /** 2495 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 2496 * use during playback. 2497 * 2498 * <p> 2499 * A notification that is noisy is more likely to be presented as a heads-up notification. 2500 * </p> 2501 * 2502 * @see Notification#sound 2503 */ 2504 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 2505 mSound = sound; 2506 mAudioAttributes = audioAttributes; 2507 return this; 2508 } 2509 2510 /** 2511 * Set the vibration pattern to use. 2512 * 2513 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 2514 * <code>pattern</code> parameter. 2515 * 2516 * <p> 2517 * A notification that vibrates is more likely to be presented as a heads-up notification. 2518 * </p> 2519 * 2520 * @see Notification#vibrate 2521 */ 2522 public Builder setVibrate(long[] pattern) { 2523 mVibrate = pattern; 2524 return this; 2525 } 2526 2527 /** 2528 * Set the desired color for the indicator LED on the device, as well as the 2529 * blink duty cycle (specified in milliseconds). 2530 * 2531 2532 * Not all devices will honor all (or even any) of these values. 2533 * 2534 2535 * @see Notification#ledARGB 2536 * @see Notification#ledOnMS 2537 * @see Notification#ledOffMS 2538 */ 2539 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 2540 mLedArgb = argb; 2541 mLedOnMs = onMs; 2542 mLedOffMs = offMs; 2543 return this; 2544 } 2545 2546 /** 2547 * Set whether this is an "ongoing" notification. 2548 * 2549 2550 * Ongoing notifications cannot be dismissed by the user, so your application or service 2551 * must take care of canceling them. 2552 * 2553 2554 * They are typically used to indicate a background task that the user is actively engaged 2555 * with (e.g., playing music) or is pending in some way and therefore occupying the device 2556 * (e.g., a file download, sync operation, active network connection). 2557 * 2558 2559 * @see Notification#FLAG_ONGOING_EVENT 2560 * @see Service#setForeground(boolean) 2561 */ 2562 public Builder setOngoing(boolean ongoing) { 2563 setFlag(FLAG_ONGOING_EVENT, ongoing); 2564 return this; 2565 } 2566 2567 /** 2568 * Set this flag if you would only like the sound, vibrate 2569 * and ticker to be played if the notification is not already showing. 2570 * 2571 * @see Notification#FLAG_ONLY_ALERT_ONCE 2572 */ 2573 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 2574 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 2575 return this; 2576 } 2577 2578 /** 2579 * Make this notification automatically dismissed when the user touches it. The 2580 * PendingIntent set with {@link #setDeleteIntent} will be sent when this happens. 2581 * 2582 * @see Notification#FLAG_AUTO_CANCEL 2583 */ 2584 public Builder setAutoCancel(boolean autoCancel) { 2585 setFlag(FLAG_AUTO_CANCEL, autoCancel); 2586 return this; 2587 } 2588 2589 /** 2590 * Set whether or not this notification should not bridge to other devices. 2591 * 2592 * <p>Some notifications can be bridged to other devices for remote display. 2593 * This hint can be set to recommend this notification not be bridged. 2594 */ 2595 public Builder setLocalOnly(boolean localOnly) { 2596 setFlag(FLAG_LOCAL_ONLY, localOnly); 2597 return this; 2598 } 2599 2600 /** 2601 * Set which notification properties will be inherited from system defaults. 2602 * <p> 2603 * The value should be one or more of the following fields combined with 2604 * bitwise-or: 2605 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 2606 * <p> 2607 * For all default values, use {@link #DEFAULT_ALL}. 2608 */ 2609 public Builder setDefaults(int defaults) { 2610 mDefaults = defaults; 2611 return this; 2612 } 2613 2614 /** 2615 * Set the priority of this notification. 2616 * 2617 * @see Notification#priority 2618 */ 2619 public Builder setPriority(@Priority int pri) { 2620 mPriority = pri; 2621 return this; 2622 } 2623 2624 /** 2625 * Set the notification category. 2626 * 2627 * @see Notification#category 2628 */ 2629 public Builder setCategory(String category) { 2630 mCategory = category; 2631 return this; 2632 } 2633 2634 /** 2635 * Add a person that is relevant to this notification. 2636 * 2637 * <P> 2638 * Depending on user preferences, this annotation may allow the notification to pass 2639 * through interruption filters, and to appear more prominently in the user interface. 2640 * </P> 2641 * 2642 * <P> 2643 * The person should be specified by the {@code String} representation of a 2644 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 2645 * </P> 2646 * 2647 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 2648 * URIs. The path part of these URIs must exist in the contacts database, in the 2649 * appropriate column, or the reference will be discarded as invalid. Telephone schema 2650 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 2651 * </P> 2652 * 2653 * @param uri A URI for the person. 2654 * @see Notification#EXTRA_PEOPLE 2655 */ 2656 public Builder addPerson(String uri) { 2657 mPeople.add(uri); 2658 return this; 2659 } 2660 2661 /** 2662 * Set this notification to be part of a group of notifications sharing the same key. 2663 * Grouped notifications may display in a cluster or stack on devices which 2664 * support such rendering. 2665 * 2666 * <p>To make this notification the summary for its group, also call 2667 * {@link #setGroupSummary}. A sort order can be specified for group members by using 2668 * {@link #setSortKey}. 2669 * @param groupKey The group key of the group. 2670 * @return this object for method chaining 2671 */ 2672 public Builder setGroup(String groupKey) { 2673 mGroupKey = groupKey; 2674 return this; 2675 } 2676 2677 /** 2678 * Set this notification to be the group summary for a group of notifications. 2679 * Grouped notifications may display in a cluster or stack on devices which 2680 * support such rendering. Requires a group key also be set using {@link #setGroup}. 2681 * @param isGroupSummary Whether this notification should be a group summary. 2682 * @return this object for method chaining 2683 */ 2684 public Builder setGroupSummary(boolean isGroupSummary) { 2685 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 2686 return this; 2687 } 2688 2689 /** 2690 * Set a sort key that orders this notification among other notifications from the 2691 * same package. This can be useful if an external sort was already applied and an app 2692 * would like to preserve this. Notifications will be sorted lexicographically using this 2693 * value, although providing different priorities in addition to providing sort key may 2694 * cause this value to be ignored. 2695 * 2696 * <p>This sort key can also be used to order members of a notification group. See 2697 * {@link #setGroup}. 2698 * 2699 * @see String#compareTo(String) 2700 */ 2701 public Builder setSortKey(String sortKey) { 2702 mSortKey = sortKey; 2703 return this; 2704 } 2705 2706 /** 2707 * Merge additional metadata into this notification. 2708 * 2709 * <p>Values within the Bundle will replace existing extras values in this Builder. 2710 * 2711 * @see Notification#extras 2712 */ 2713 public Builder addExtras(Bundle extras) { 2714 if (extras != null) { 2715 if (mExtras == null) { 2716 mExtras = new Bundle(extras); 2717 } else { 2718 mExtras.putAll(extras); 2719 } 2720 } 2721 return this; 2722 } 2723 2724 /** 2725 * Set metadata for this notification. 2726 * 2727 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 2728 * current contents are copied into the Notification each time {@link #build()} is 2729 * called. 2730 * 2731 * <p>Replaces any existing extras values with those from the provided Bundle. 2732 * Use {@link #addExtras} to merge in metadata instead. 2733 * 2734 * @see Notification#extras 2735 */ 2736 public Builder setExtras(Bundle extras) { 2737 mExtras = extras; 2738 return this; 2739 } 2740 2741 /** 2742 * Get the current metadata Bundle used by this notification Builder. 2743 * 2744 * <p>The returned Bundle is shared with this Builder. 2745 * 2746 * <p>The current contents of this Bundle are copied into the Notification each time 2747 * {@link #build()} is called. 2748 * 2749 * @see Notification#extras 2750 */ 2751 public Bundle getExtras() { 2752 if (mExtras == null) { 2753 mExtras = new Bundle(); 2754 } 2755 return mExtras; 2756 } 2757 2758 /** 2759 * Add an action to this notification. Actions are typically displayed by 2760 * the system as a button adjacent to the notification content. 2761 * <p> 2762 * Every action must have an icon (32dp square and matching the 2763 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 2764 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 2765 * <p> 2766 * A notification in its expanded form can display up to 3 actions, from left to right in 2767 * the order they were added. Actions will not be displayed when the notification is 2768 * collapsed, however, so be sure that any essential functions may be accessed by the user 2769 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 2770 * 2771 * @param icon Resource ID of a drawable that represents the action. 2772 * @param title Text describing the action. 2773 * @param intent PendingIntent to be fired when the action is invoked. 2774 * 2775 * @deprecated Use {@link #addAction(Action)} instead. 2776 */ 2777 @Deprecated 2778 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 2779 mActions.add(new Action(icon, safeCharSequence(title), intent)); 2780 return this; 2781 } 2782 2783 /** 2784 * Add an action to this notification. Actions are typically displayed by 2785 * the system as a button adjacent to the notification content. 2786 * <p> 2787 * Every action must have an icon (32dp square and matching the 2788 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 2789 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 2790 * <p> 2791 * A notification in its expanded form can display up to 3 actions, from left to right in 2792 * the order they were added. Actions will not be displayed when the notification is 2793 * collapsed, however, so be sure that any essential functions may be accessed by the user 2794 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 2795 * 2796 * @param action The action to add. 2797 */ 2798 public Builder addAction(Action action) { 2799 mActions.add(action); 2800 return this; 2801 } 2802 2803 /** 2804 * Add a rich notification style to be applied at build time. 2805 * 2806 * @param style Object responsible for modifying the notification style. 2807 */ 2808 public Builder setStyle(Style style) { 2809 if (mStyle != style) { 2810 mStyle = style; 2811 if (mStyle != null) { 2812 mStyle.setBuilder(this); 2813 } 2814 } 2815 return this; 2816 } 2817 2818 /** 2819 * Specify the value of {@link #visibility}. 2820 * 2821 * @param visibility One of {@link #VISIBILITY_PRIVATE} (the default), 2822 * {@link #VISIBILITY_SECRET}, or {@link #VISIBILITY_PUBLIC}. 2823 * 2824 * @return The same Builder. 2825 */ 2826 public Builder setVisibility(int visibility) { 2827 mVisibility = visibility; 2828 return this; 2829 } 2830 2831 /** 2832 * Supply a replacement Notification whose contents should be shown in insecure contexts 2833 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 2834 * @param n A replacement notification, presumably with some or all info redacted. 2835 * @return The same Builder. 2836 */ 2837 public Builder setPublicVersion(Notification n) { 2838 mPublicVersion = n; 2839 return this; 2840 } 2841 2842 /** 2843 * Apply an extender to this notification builder. Extenders may be used to add 2844 * metadata or change options on this builder. 2845 */ 2846 public Builder extend(Extender extender) { 2847 extender.extend(this); 2848 return this; 2849 } 2850 2851 /** 2852 * @hide 2853 */ 2854 public void setFlag(int mask, boolean value) { 2855 if (value) { 2856 mFlags |= mask; 2857 } else { 2858 mFlags &= ~mask; 2859 } 2860 } 2861 2862 /** 2863 * Sets {@link Notification#color}. 2864 * 2865 * @param argb The accent color to use 2866 * 2867 * @return The same Builder. 2868 */ 2869 public Builder setColor(@ColorInt int argb) { 2870 mColor = argb; 2871 return this; 2872 } 2873 2874 private Drawable getProfileBadgeDrawable() { 2875 // Note: This assumes that the current user can read the profile badge of the 2876 // originating user. 2877 return mContext.getPackageManager().getUserBadgeForDensity( 2878 new UserHandle(mOriginatingUserId), 0); 2879 } 2880 2881 private Bitmap getProfileBadge() { 2882 Drawable badge = getProfileBadgeDrawable(); 2883 if (badge == null) { 2884 return null; 2885 } 2886 final int size = mContext.getResources().getDimensionPixelSize( 2887 R.dimen.notification_badge_size); 2888 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 2889 Canvas canvas = new Canvas(bitmap); 2890 badge.setBounds(0, 0, size, size); 2891 badge.draw(canvas); 2892 return bitmap; 2893 } 2894 2895 private boolean addProfileBadge(RemoteViews contentView, int resId) { 2896 Bitmap profileBadge = getProfileBadge(); 2897 2898 contentView.setViewVisibility(R.id.profile_badge_large_template, View.GONE); 2899 contentView.setViewVisibility(R.id.profile_badge_line2, View.GONE); 2900 contentView.setViewVisibility(R.id.profile_badge_line3, View.GONE); 2901 2902 if (profileBadge != null) { 2903 contentView.setImageViewBitmap(resId, profileBadge); 2904 contentView.setViewVisibility(resId, View.VISIBLE); 2905 2906 // Make sure Line 3 is visible. As badge will be here if there 2907 // is no text to display. 2908 if (resId == R.id.profile_badge_line3) { 2909 contentView.setViewVisibility(R.id.line3, View.VISIBLE); 2910 } 2911 return true; 2912 } 2913 return false; 2914 } 2915 2916 private void shrinkLine3Text(RemoteViews contentView) { 2917 float subTextSize = mContext.getResources().getDimensionPixelSize( 2918 R.dimen.notification_subtext_size); 2919 contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize); 2920 } 2921 2922 private void unshrinkLine3Text(RemoteViews contentView) { 2923 float regularTextSize = mContext.getResources().getDimensionPixelSize( 2924 com.android.internal.R.dimen.notification_text_size); 2925 contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, regularTextSize); 2926 } 2927 2928 private void resetStandardTemplate(RemoteViews contentView) { 2929 removeLargeIconBackground(contentView); 2930 contentView.setViewPadding(R.id.icon, 0, 0, 0, 0); 2931 contentView.setImageViewResource(R.id.icon, 0); 2932 contentView.setInt(R.id.icon, "setBackgroundResource", 0); 2933 contentView.setViewVisibility(R.id.right_icon, View.GONE); 2934 contentView.setInt(R.id.right_icon, "setBackgroundResource", 0); 2935 contentView.setImageViewResource(R.id.right_icon, 0); 2936 contentView.setImageViewResource(R.id.icon, 0); 2937 contentView.setTextViewText(R.id.title, null); 2938 contentView.setTextViewText(R.id.text, null); 2939 unshrinkLine3Text(contentView); 2940 contentView.setTextViewText(R.id.text2, null); 2941 contentView.setViewVisibility(R.id.text2, View.GONE); 2942 contentView.setViewVisibility(R.id.info, View.GONE); 2943 contentView.setViewVisibility(R.id.time, View.GONE); 2944 contentView.setViewVisibility(R.id.line3, View.GONE); 2945 contentView.setViewVisibility(R.id.overflow_divider, View.GONE); 2946 contentView.setViewVisibility(R.id.progress, View.GONE); 2947 contentView.setViewVisibility(R.id.chronometer, View.GONE); 2948 contentView.setViewVisibility(R.id.time, View.GONE); 2949 } 2950 2951 private RemoteViews applyStandardTemplate(int resId) { 2952 return applyStandardTemplate(resId, true /* hasProgress */); 2953 } 2954 2955 /** 2956 * @param hasProgress whether the progress bar should be shown and set 2957 */ 2958 private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) { 2959 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 2960 2961 resetStandardTemplate(contentView); 2962 2963 boolean showLine3 = false; 2964 boolean showLine2 = false; 2965 boolean contentTextInLine2 = false; 2966 2967 if (mLargeIcon != null) { 2968 contentView.setImageViewIcon(R.id.icon, mLargeIcon); 2969 processLargeLegacyIcon(mLargeIcon, contentView); 2970 contentView.setImageViewIcon(R.id.right_icon, mSmallIcon); 2971 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 2972 processSmallRightIcon(mSmallIcon, contentView); 2973 } else { // small icon at left 2974 contentView.setImageViewIcon(R.id.icon, mSmallIcon); 2975 contentView.setViewVisibility(R.id.icon, View.VISIBLE); 2976 processSmallIconAsLarge(mSmallIcon, contentView); 2977 } 2978 if (mContentTitle != null) { 2979 contentView.setTextViewText(R.id.title, processLegacyText(mContentTitle)); 2980 } 2981 if (mContentText != null) { 2982 contentView.setTextViewText(R.id.text, processLegacyText(mContentText)); 2983 showLine3 = true; 2984 } 2985 if (mContentInfo != null) { 2986 contentView.setTextViewText(R.id.info, processLegacyText(mContentInfo)); 2987 contentView.setViewVisibility(R.id.info, View.VISIBLE); 2988 showLine3 = true; 2989 } else if (mNumber > 0) { 2990 final int tooBig = mContext.getResources().getInteger( 2991 R.integer.status_bar_notification_info_maxnum); 2992 if (mNumber > tooBig) { 2993 contentView.setTextViewText(R.id.info, processLegacyText( 2994 mContext.getResources().getString( 2995 R.string.status_bar_notification_info_overflow))); 2996 } else { 2997 NumberFormat f = NumberFormat.getIntegerInstance(); 2998 contentView.setTextViewText(R.id.info, processLegacyText(f.format(mNumber))); 2999 } 3000 contentView.setViewVisibility(R.id.info, View.VISIBLE); 3001 showLine3 = true; 3002 } else { 3003 contentView.setViewVisibility(R.id.info, View.GONE); 3004 } 3005 3006 // Need to show three lines? 3007 if (mSubText != null) { 3008 contentView.setTextViewText(R.id.text, processLegacyText(mSubText)); 3009 if (mContentText != null) { 3010 contentView.setTextViewText(R.id.text2, processLegacyText(mContentText)); 3011 contentView.setViewVisibility(R.id.text2, View.VISIBLE); 3012 showLine2 = true; 3013 contentTextInLine2 = true; 3014 } else { 3015 contentView.setViewVisibility(R.id.text2, View.GONE); 3016 } 3017 } else { 3018 contentView.setViewVisibility(R.id.text2, View.GONE); 3019 if (hasProgress && (mProgressMax != 0 || mProgressIndeterminate)) { 3020 contentView.setViewVisibility(R.id.progress, View.VISIBLE); 3021 contentView.setProgressBar( 3022 R.id.progress, mProgressMax, mProgress, mProgressIndeterminate); 3023 contentView.setProgressBackgroundTintList( 3024 R.id.progress, ColorStateList.valueOf(mContext.getColor( 3025 R.color.notification_progress_background_color))); 3026 if (mColor != COLOR_DEFAULT) { 3027 ColorStateList colorStateList = ColorStateList.valueOf(mColor); 3028 contentView.setProgressTintList(R.id.progress, colorStateList); 3029 contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); 3030 } 3031 showLine2 = true; 3032 } else { 3033 contentView.setViewVisibility(R.id.progress, View.GONE); 3034 } 3035 } 3036 if (showLine2) { 3037 3038 // need to shrink all the type to make sure everything fits 3039 shrinkLine3Text(contentView); 3040 } 3041 3042 if (showsTimeOrChronometer()) { 3043 if (mUseChronometer) { 3044 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 3045 contentView.setLong(R.id.chronometer, "setBase", 3046 mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 3047 contentView.setBoolean(R.id.chronometer, "setStarted", true); 3048 } else { 3049 contentView.setViewVisibility(R.id.time, View.VISIBLE); 3050 contentView.setLong(R.id.time, "setTime", mWhen); 3051 } 3052 } 3053 3054 // Adjust padding depending on line count and font size. 3055 contentView.setViewPadding(R.id.line1, 0, calculateTopPadding(mContext, 3056 mHasThreeLines, mContext.getResources().getConfiguration().fontScale), 3057 0, 0); 3058 3059 // We want to add badge to first line of text. 3060 boolean addedBadge = addProfileBadge(contentView, 3061 contentTextInLine2 ? R.id.profile_badge_line2 : R.id.profile_badge_line3); 3062 // If we added the badge to line 3 then we should show line 3. 3063 if (addedBadge && !contentTextInLine2) { 3064 showLine3 = true; 3065 } 3066 3067 // Note getStandardView may hide line 3 again. 3068 contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); 3069 contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE); 3070 return contentView; 3071 } 3072 3073 /** 3074 * @return true if the built notification will show the time or the chronometer; false 3075 * otherwise 3076 */ showsTimeOrChronometer()3077 private boolean showsTimeOrChronometer() { 3078 return mWhen != 0 && mShowWhen; 3079 } 3080 3081 /** 3082 * Logic to find out whether the notification is going to have three lines in the contracted 3083 * layout. This is used to adjust the top padding. 3084 * 3085 * @return true if the notification is going to have three lines; false if the notification 3086 * is going to have one or two lines 3087 */ hasThreeLines()3088 private boolean hasThreeLines() { 3089 boolean contentTextInLine2 = mSubText != null && mContentText != null; 3090 boolean hasProgress = mStyle == null || mStyle.hasProgress(); 3091 // If we have content text in line 2, badge goes into line 2, or line 3 otherwise 3092 boolean badgeInLine3 = getProfileBadgeDrawable() != null && !contentTextInLine2; 3093 boolean hasLine3 = mContentText != null || mContentInfo != null || mNumber > 0 3094 || badgeInLine3; 3095 boolean hasLine2 = (mSubText != null && mContentText != null) || 3096 (hasProgress && mSubText == null 3097 && (mProgressMax != 0 || mProgressIndeterminate)); 3098 return hasLine2 && hasLine3; 3099 } 3100 3101 /** 3102 * @hide 3103 */ calculateTopPadding(Context ctx, boolean hasThreeLines, float fontScale)3104 public static int calculateTopPadding(Context ctx, boolean hasThreeLines, 3105 float fontScale) { 3106 int padding = ctx.getResources().getDimensionPixelSize(hasThreeLines 3107 ? R.dimen.notification_top_pad_narrow 3108 : R.dimen.notification_top_pad); 3109 int largePadding = ctx.getResources().getDimensionPixelSize(hasThreeLines 3110 ? R.dimen.notification_top_pad_large_text_narrow 3111 : R.dimen.notification_top_pad_large_text); 3112 float largeFactor = (MathUtils.constrain(fontScale, 1.0f, LARGE_TEXT_SCALE) - 1f) 3113 / (LARGE_TEXT_SCALE - 1f); 3114 3115 // Linearly interpolate the padding between large and normal with the font scale ranging 3116 // from 1f to LARGE_TEXT_SCALE 3117 return Math.round((1 - largeFactor) * padding + largeFactor * largePadding); 3118 } 3119 resetStandardTemplateWithActions(RemoteViews big)3120 private void resetStandardTemplateWithActions(RemoteViews big) { 3121 big.setViewVisibility(R.id.actions, View.GONE); 3122 big.setViewVisibility(R.id.action_divider, View.GONE); 3123 big.removeAllViews(R.id.actions); 3124 } 3125 applyStandardTemplateWithActions(int layoutId)3126 private RemoteViews applyStandardTemplateWithActions(int layoutId) { 3127 RemoteViews big = applyStandardTemplate(layoutId); 3128 3129 resetStandardTemplateWithActions(big); 3130 3131 int N = mActions.size(); 3132 if (N > 0) { 3133 big.setViewVisibility(R.id.actions, View.VISIBLE); 3134 big.setViewVisibility(R.id.action_divider, View.VISIBLE); 3135 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; 3136 for (int i=0; i<N; i++) { 3137 final RemoteViews button = generateActionButton(mActions.get(i)); 3138 big.addView(R.id.actions, button); 3139 } 3140 } 3141 return big; 3142 } 3143 makeContentView()3144 private RemoteViews makeContentView() { 3145 if (mContentView != null) { 3146 return mContentView; 3147 } else { 3148 return applyStandardTemplate(getBaseLayoutResource()); 3149 } 3150 } 3151 makeTickerView()3152 private RemoteViews makeTickerView() { 3153 if (mTickerView != null) { 3154 return mTickerView; 3155 } 3156 return null; // tickers are not created by default anymore 3157 } 3158 makeBigContentView()3159 private RemoteViews makeBigContentView() { 3160 if (mActions.size() == 0) return null; 3161 3162 return applyStandardTemplateWithActions(getBigBaseLayoutResource()); 3163 } 3164 makeHeadsUpContentView()3165 private RemoteViews makeHeadsUpContentView() { 3166 if (mActions.size() == 0) return null; 3167 3168 return applyStandardTemplateWithActions(getBigBaseLayoutResource()); 3169 } 3170 3171 generateActionButton(Action action)3172 private RemoteViews generateActionButton(Action action) { 3173 final boolean tombstone = (action.actionIntent == null); 3174 RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 3175 tombstone ? getActionTombstoneLayoutResource() 3176 : getActionLayoutResource()); 3177 final Icon ai = action.getIcon(); 3178 button.setTextViewCompoundDrawablesRelative(R.id.action0, ai, null, null, null); 3179 button.setTextViewText(R.id.action0, processLegacyText(action.title)); 3180 if (!tombstone) { 3181 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 3182 } 3183 button.setContentDescription(R.id.action0, action.title); 3184 processLegacyAction(action, button); 3185 return button; 3186 } 3187 3188 /** 3189 * @return Whether we are currently building a notification from a legacy (an app that 3190 * doesn't create material notifications by itself) app. 3191 */ isLegacy()3192 private boolean isLegacy() { 3193 return mColorUtil != null; 3194 } 3195 processLegacyAction(Action action, RemoteViews button)3196 private void processLegacyAction(Action action, RemoteViews button) { 3197 if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.getIcon())) { 3198 button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0, 3199 mContext.getColor(R.color.notification_action_color_filter), 3200 PorterDuff.Mode.MULTIPLY); 3201 } 3202 } 3203 processLegacyText(CharSequence charSequence)3204 private CharSequence processLegacyText(CharSequence charSequence) { 3205 if (isLegacy()) { 3206 return mColorUtil.invertCharSequenceColors(charSequence); 3207 } else { 3208 return charSequence; 3209 } 3210 } 3211 3212 /** 3213 * Apply any necessary background to smallIcons being used in the largeIcon spot. 3214 */ processSmallIconAsLarge(Icon largeIcon, RemoteViews contentView)3215 private void processSmallIconAsLarge(Icon largeIcon, RemoteViews contentView) { 3216 if (!isLegacy()) { 3217 contentView.setDrawableParameters(R.id.icon, false, -1, 3218 0xFFFFFFFF, 3219 PorterDuff.Mode.SRC_ATOP, -1); 3220 applyLargeIconBackground(contentView); 3221 } else { 3222 if (mColorUtil.isGrayscaleIcon(mContext, largeIcon)) { 3223 applyLargeIconBackground(contentView); 3224 } 3225 } 3226 } 3227 3228 /** 3229 * Apply any necessary background to a largeIcon if it's a fake smallIcon (that is, 3230 * if it's grayscale). 3231 */ 3232 // TODO: also check bounds, transparency, that sort of thing. processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView)3233 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView) { 3234 if (largeIcon != null && isLegacy() 3235 && mColorUtil.isGrayscaleIcon(mContext, largeIcon)) { 3236 applyLargeIconBackground(contentView); 3237 } else { 3238 removeLargeIconBackground(contentView); 3239 } 3240 } 3241 3242 /** 3243 * Add a colored circle behind the largeIcon slot. 3244 */ applyLargeIconBackground(RemoteViews contentView)3245 private void applyLargeIconBackground(RemoteViews contentView) { 3246 contentView.setInt(R.id.icon, "setBackgroundResource", 3247 R.drawable.notification_icon_legacy_bg); 3248 3249 contentView.setDrawableParameters( 3250 R.id.icon, 3251 true, 3252 -1, 3253 resolveColor(), 3254 PorterDuff.Mode.SRC_ATOP, 3255 -1); 3256 3257 int padding = mContext.getResources().getDimensionPixelSize( 3258 R.dimen.notification_large_icon_circle_padding); 3259 contentView.setViewPadding(R.id.icon, padding, padding, padding, padding); 3260 } 3261 removeLargeIconBackground(RemoteViews contentView)3262 private void removeLargeIconBackground(RemoteViews contentView) { 3263 contentView.setInt(R.id.icon, "setBackgroundResource", 0); 3264 } 3265 3266 /** 3267 * Recolor small icons when used in the R.id.right_icon slot. 3268 */ processSmallRightIcon(Icon smallIcon, RemoteViews contentView)3269 private void processSmallRightIcon(Icon smallIcon, RemoteViews contentView) { 3270 if (!isLegacy()) { 3271 contentView.setDrawableParameters(R.id.right_icon, false, -1, 3272 0xFFFFFFFF, 3273 PorterDuff.Mode.SRC_ATOP, -1); 3274 } 3275 final boolean gray = isLegacy() 3276 && smallIcon.getType() == Icon.TYPE_RESOURCE 3277 && mColorUtil.isGrayscaleIcon(mContext, smallIcon.getResId()); 3278 if (!isLegacy() || gray) { 3279 contentView.setInt(R.id.right_icon, 3280 "setBackgroundResource", 3281 R.drawable.notification_icon_legacy_bg); 3282 3283 contentView.setDrawableParameters( 3284 R.id.right_icon, 3285 true, 3286 -1, 3287 resolveColor(), 3288 PorterDuff.Mode.SRC_ATOP, 3289 -1); 3290 } 3291 } 3292 sanitizeColor()3293 private int sanitizeColor() { 3294 if (mColor != COLOR_DEFAULT) { 3295 mColor |= 0xFF000000; // no alpha for custom colors 3296 } 3297 return mColor; 3298 } 3299 resolveColor()3300 private int resolveColor() { 3301 if (mColor == COLOR_DEFAULT) { 3302 return mContext.getColor(R.color.notification_icon_bg_color); 3303 } 3304 return mColor; 3305 } 3306 3307 /** 3308 * Apply the unstyled operations and return a new {@link Notification} object. 3309 * @hide 3310 */ buildUnstyled()3311 public Notification buildUnstyled() { 3312 Notification n = new Notification(); 3313 n.when = mWhen; 3314 n.mSmallIcon = mSmallIcon; 3315 if (mSmallIcon != null && mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 3316 n.icon = mSmallIcon.getResId(); 3317 } 3318 n.iconLevel = mSmallIconLevel; 3319 n.number = mNumber; 3320 3321 n.color = sanitizeColor(); 3322 3323 setBuilderContentView(n, makeContentView()); 3324 n.contentIntent = mContentIntent; 3325 n.deleteIntent = mDeleteIntent; 3326 n.fullScreenIntent = mFullScreenIntent; 3327 n.tickerText = mTickerText; 3328 n.tickerView = makeTickerView(); 3329 n.mLargeIcon = mLargeIcon; 3330 if (mLargeIcon != null && mLargeIcon.getType() == Icon.TYPE_BITMAP) { 3331 n.largeIcon = mLargeIcon.getBitmap(); 3332 } 3333 n.sound = mSound; 3334 n.audioStreamType = mAudioStreamType; 3335 n.audioAttributes = mAudioAttributes; 3336 n.vibrate = mVibrate; 3337 n.ledARGB = mLedArgb; 3338 n.ledOnMS = mLedOnMs; 3339 n.ledOffMS = mLedOffMs; 3340 n.defaults = mDefaults; 3341 n.flags = mFlags; 3342 setBuilderBigContentView(n, makeBigContentView()); 3343 setBuilderHeadsUpContentView(n, makeHeadsUpContentView()); 3344 if (mLedOnMs != 0 || mLedOffMs != 0) { 3345 n.flags |= FLAG_SHOW_LIGHTS; 3346 } 3347 if ((mDefaults & DEFAULT_LIGHTS) != 0) { 3348 n.flags |= FLAG_SHOW_LIGHTS; 3349 } 3350 n.category = mCategory; 3351 n.mGroupKey = mGroupKey; 3352 n.mSortKey = mSortKey; 3353 n.priority = mPriority; 3354 if (mActions.size() > 0) { 3355 n.actions = new Action[mActions.size()]; 3356 mActions.toArray(n.actions); 3357 } 3358 n.visibility = mVisibility; 3359 3360 if (mPublicVersion != null) { 3361 n.publicVersion = new Notification(); 3362 mPublicVersion.cloneInto(n.publicVersion, true); 3363 } 3364 // Note: If you're adding new fields, also update restoreFromNotitification(). 3365 return n; 3366 } 3367 3368 /** 3369 * Capture, in the provided bundle, semantic information used in the construction of 3370 * this Notification object. 3371 * @hide 3372 */ populateExtras(Bundle extras)3373 public void populateExtras(Bundle extras) { 3374 // Store original information used in the construction of this object 3375 extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId); 3376 extras.putParcelable(EXTRA_REBUILD_CONTEXT_APPLICATION_INFO, 3377 mContext.getApplicationInfo()); 3378 extras.putCharSequence(EXTRA_TITLE, mContentTitle); 3379 extras.putCharSequence(EXTRA_TEXT, mContentText); 3380 extras.putCharSequence(EXTRA_SUB_TEXT, mSubText); 3381 extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo); 3382 extras.putParcelable(EXTRA_SMALL_ICON, mSmallIcon); 3383 extras.putInt(EXTRA_PROGRESS, mProgress); 3384 extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax); 3385 extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate); 3386 extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer); 3387 extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen); 3388 if (mLargeIcon != null) { 3389 extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 3390 } 3391 if (!mPeople.isEmpty()) { 3392 extras.putStringArray(EXTRA_PEOPLE, mPeople.toArray(new String[mPeople.size()])); 3393 } 3394 // NOTE: If you're adding new extras also update restoreFromNotification(). 3395 } 3396 3397 3398 /** 3399 * @hide 3400 */ stripForDelivery(Notification n)3401 public static void stripForDelivery(Notification n) { 3402 if (!STRIP_AND_REBUILD) { 3403 return; 3404 } 3405 3406 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 3407 // Only strip views for known Styles because we won't know how to 3408 // re-create them otherwise. 3409 boolean stripViews = TextUtils.isEmpty(templateClass) || 3410 getNotificationStyleClass(templateClass) != null; 3411 3412 boolean isStripped = false; 3413 3414 if (n.largeIcon != null && n.extras.containsKey(EXTRA_LARGE_ICON)) { 3415 // TODO: Would like to check for equality here, but if the notification 3416 // has been cloned, we can't. 3417 n.largeIcon = null; 3418 n.extras.putBoolean(EXTRA_REBUILD_LARGE_ICON, true); 3419 isStripped = true; 3420 } 3421 // Get rid of unmodified BuilderRemoteViews. 3422 3423 if (stripViews && 3424 n.contentView instanceof BuilderRemoteViews && 3425 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 3426 n.contentView.getSequenceNumber()) { 3427 n.contentView = null; 3428 n.extras.putBoolean(EXTRA_REBUILD_CONTENT_VIEW, true); 3429 n.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 3430 isStripped = true; 3431 } 3432 if (stripViews && 3433 n.bigContentView instanceof BuilderRemoteViews && 3434 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 3435 n.bigContentView.getSequenceNumber()) { 3436 n.bigContentView = null; 3437 n.extras.putBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW, true); 3438 n.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 3439 isStripped = true; 3440 } 3441 if (stripViews && 3442 n.headsUpContentView instanceof BuilderRemoteViews && 3443 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 3444 n.headsUpContentView.getSequenceNumber()) { 3445 n.headsUpContentView = null; 3446 n.extras.putBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW, true); 3447 n.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 3448 isStripped = true; 3449 } 3450 3451 if (isStripped) { 3452 n.extras.putBoolean(EXTRA_NEEDS_REBUILD, true); 3453 } 3454 } 3455 3456 /** 3457 * @hide 3458 */ rebuild(Context context, Notification n)3459 public static Notification rebuild(Context context, Notification n) { 3460 Bundle extras = n.extras; 3461 if (!extras.getBoolean(EXTRA_NEEDS_REBUILD)) return n; 3462 extras.remove(EXTRA_NEEDS_REBUILD); 3463 3464 // Re-create notification context so we can access app resources. 3465 ApplicationInfo applicationInfo = extras.getParcelable( 3466 EXTRA_REBUILD_CONTEXT_APPLICATION_INFO); 3467 Context builderContext; 3468 try { 3469 builderContext = context.createApplicationContext(applicationInfo, 3470 Context.CONTEXT_RESTRICTED); 3471 } catch (NameNotFoundException e) { 3472 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 3473 builderContext = context; // try with our context 3474 } 3475 3476 Builder b = new Builder(builderContext, n); 3477 return b.rebuild(); 3478 } 3479 3480 /** 3481 * Rebuilds the notification passed in to the rebuild-constructor 3482 * {@link #Builder(Context, Notification)}. 3483 * 3484 * <p> 3485 * Throws IllegalStateException when invoked on a Builder that isn't in rebuild mode. 3486 * 3487 * @hide 3488 */ rebuild()3489 private Notification rebuild() { 3490 if (mRebuildNotification == null) { 3491 throw new IllegalStateException("rebuild() only valid when in 'rebuild' mode."); 3492 } 3493 mHasThreeLines = hasThreeLines(); 3494 3495 Bundle extras = mRebuildNotification.extras; 3496 3497 if (extras.getBoolean(EXTRA_REBUILD_LARGE_ICON)) { 3498 mRebuildNotification.largeIcon = extras.getParcelable(EXTRA_LARGE_ICON); 3499 } 3500 extras.remove(EXTRA_REBUILD_LARGE_ICON); 3501 3502 if (extras.getBoolean(EXTRA_REBUILD_CONTENT_VIEW)) { 3503 setBuilderContentView(mRebuildNotification, makeContentView()); 3504 if (mStyle != null) { 3505 mStyle.populateContentView(mRebuildNotification); 3506 } 3507 } 3508 extras.remove(EXTRA_REBUILD_CONTENT_VIEW); 3509 3510 if (extras.getBoolean(EXTRA_REBUILD_BIG_CONTENT_VIEW)) { 3511 setBuilderBigContentView(mRebuildNotification, makeBigContentView()); 3512 if (mStyle != null) { 3513 mStyle.populateBigContentView(mRebuildNotification); 3514 } 3515 } 3516 extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW); 3517 3518 if (extras.getBoolean(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW)) { 3519 setBuilderHeadsUpContentView(mRebuildNotification, makeHeadsUpContentView()); 3520 if (mStyle != null) { 3521 mStyle.populateHeadsUpContentView(mRebuildNotification); 3522 } 3523 } 3524 extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW); 3525 3526 mHasThreeLines = false; 3527 return mRebuildNotification; 3528 } 3529 getNotificationStyleClass(String templateClass)3530 private static Class<? extends Style> getNotificationStyleClass(String templateClass) { 3531 Class<? extends Style>[] classes = new Class[]{ 3532 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class}; 3533 for (Class<? extends Style> innerClass : classes) { 3534 if (templateClass.equals(innerClass.getName())) { 3535 return innerClass; 3536 } 3537 } 3538 return null; 3539 } 3540 setBuilderContentView(Notification n, RemoteViews contentView)3541 private void setBuilderContentView(Notification n, RemoteViews contentView) { 3542 n.contentView = contentView; 3543 if (contentView instanceof BuilderRemoteViews) { 3544 mRebuildBundle.putInt(Builder.EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 3545 contentView.getSequenceNumber()); 3546 } 3547 } 3548 setBuilderBigContentView(Notification n, RemoteViews bigContentView)3549 private void setBuilderBigContentView(Notification n, RemoteViews bigContentView) { 3550 n.bigContentView = bigContentView; 3551 if (bigContentView instanceof BuilderRemoteViews) { 3552 mRebuildBundle.putInt(Builder.EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 3553 bigContentView.getSequenceNumber()); 3554 } 3555 } 3556 setBuilderHeadsUpContentView(Notification n, RemoteViews headsUpContentView)3557 private void setBuilderHeadsUpContentView(Notification n, 3558 RemoteViews headsUpContentView) { 3559 n.headsUpContentView = headsUpContentView; 3560 if (headsUpContentView instanceof BuilderRemoteViews) { 3561 mRebuildBundle.putInt(Builder.EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 3562 headsUpContentView.getSequenceNumber()); 3563 } 3564 } 3565 restoreFromNotification(Notification n)3566 private void restoreFromNotification(Notification n) { 3567 3568 // Notification fields. 3569 mWhen = n.when; 3570 mSmallIcon = n.mSmallIcon; 3571 mSmallIconLevel = n.iconLevel; 3572 mNumber = n.number; 3573 3574 mColor = n.color; 3575 3576 mContentView = n.contentView; 3577 mDeleteIntent = n.deleteIntent; 3578 mFullScreenIntent = n.fullScreenIntent; 3579 mTickerText = n.tickerText; 3580 mTickerView = n.tickerView; 3581 mLargeIcon = n.mLargeIcon; 3582 mSound = n.sound; 3583 mAudioStreamType = n.audioStreamType; 3584 mAudioAttributes = n.audioAttributes; 3585 3586 mVibrate = n.vibrate; 3587 mLedArgb = n.ledARGB; 3588 mLedOnMs = n.ledOnMS; 3589 mLedOffMs = n.ledOffMS; 3590 mDefaults = n.defaults; 3591 mFlags = n.flags; 3592 3593 mCategory = n.category; 3594 mGroupKey = n.mGroupKey; 3595 mSortKey = n.mSortKey; 3596 mPriority = n.priority; 3597 mActions.clear(); 3598 if (n.actions != null) { 3599 Collections.addAll(mActions, n.actions); 3600 } 3601 mVisibility = n.visibility; 3602 3603 mPublicVersion = n.publicVersion; 3604 3605 // Extras. 3606 Bundle extras = n.extras; 3607 mOriginatingUserId = extras.getInt(EXTRA_ORIGINATING_USERID); 3608 mContentTitle = extras.getCharSequence(EXTRA_TITLE); 3609 mContentText = extras.getCharSequence(EXTRA_TEXT); 3610 mSubText = extras.getCharSequence(EXTRA_SUB_TEXT); 3611 mContentInfo = extras.getCharSequence(EXTRA_INFO_TEXT); 3612 mProgress = extras.getInt(EXTRA_PROGRESS); 3613 mProgressMax = extras.getInt(EXTRA_PROGRESS_MAX); 3614 mProgressIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 3615 mUseChronometer = extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 3616 mShowWhen = extras.getBoolean(EXTRA_SHOW_WHEN); 3617 if (extras.containsKey(EXTRA_LARGE_ICON)) { 3618 mLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON); 3619 } 3620 if (extras.containsKey(EXTRA_PEOPLE)) { 3621 mPeople.clear(); 3622 Collections.addAll(mPeople, extras.getStringArray(EXTRA_PEOPLE)); 3623 } 3624 } 3625 3626 /** 3627 * @deprecated Use {@link #build()} instead. 3628 */ 3629 @Deprecated getNotification()3630 public Notification getNotification() { 3631 return build(); 3632 } 3633 3634 /** 3635 * Combine all of the options that have been set and return a new {@link Notification} 3636 * object. 3637 */ build()3638 public Notification build() { 3639 if (mSmallIcon != null) { 3640 mSmallIcon.convertToAshmem(); 3641 } 3642 if (mLargeIcon != null) { 3643 mLargeIcon.convertToAshmem(); 3644 } 3645 mOriginatingUserId = mContext.getUserId(); 3646 mHasThreeLines = hasThreeLines(); 3647 3648 Notification n = buildUnstyled(); 3649 3650 if (mStyle != null) { 3651 mStyle.purgeResources(); 3652 n = mStyle.buildStyled(n); 3653 } 3654 3655 if (mExtras != null) { 3656 n.extras.putAll(mExtras); 3657 } 3658 3659 if (mRebuildBundle.size() > 0) { 3660 n.extras.putAll(mRebuildBundle); 3661 mRebuildBundle.clear(); 3662 } 3663 3664 populateExtras(n.extras); 3665 if (mStyle != null) { 3666 mStyle.addExtras(n.extras); 3667 } 3668 3669 mHasThreeLines = false; 3670 return n; 3671 } 3672 3673 /** 3674 * Apply this Builder to an existing {@link Notification} object. 3675 * 3676 * @hide 3677 */ buildInto(Notification n)3678 public Notification buildInto(Notification n) { 3679 build().cloneInto(n, true); 3680 return n; 3681 } 3682 getBaseLayoutResource()3683 private int getBaseLayoutResource() { 3684 return R.layout.notification_template_material_base; 3685 } 3686 getBigBaseLayoutResource()3687 private int getBigBaseLayoutResource() { 3688 return R.layout.notification_template_material_big_base; 3689 } 3690 getBigPictureLayoutResource()3691 private int getBigPictureLayoutResource() { 3692 return R.layout.notification_template_material_big_picture; 3693 } 3694 getBigTextLayoutResource()3695 private int getBigTextLayoutResource() { 3696 return R.layout.notification_template_material_big_text; 3697 } 3698 getInboxLayoutResource()3699 private int getInboxLayoutResource() { 3700 return R.layout.notification_template_material_inbox; 3701 } 3702 getActionLayoutResource()3703 private int getActionLayoutResource() { 3704 return R.layout.notification_material_action; 3705 } 3706 getActionTombstoneLayoutResource()3707 private int getActionTombstoneLayoutResource() { 3708 return R.layout.notification_material_action_tombstone; 3709 } 3710 } 3711 3712 /** 3713 * An object that can apply a rich notification style to a {@link Notification.Builder} 3714 * object. 3715 */ 3716 public static abstract class Style { 3717 private CharSequence mBigContentTitle; 3718 3719 /** 3720 * @hide 3721 */ 3722 protected CharSequence mSummaryText = null; 3723 3724 /** 3725 * @hide 3726 */ 3727 protected boolean mSummaryTextSet = false; 3728 3729 protected Builder mBuilder; 3730 3731 /** 3732 * Overrides ContentTitle in the big form of the template. 3733 * This defaults to the value passed to setContentTitle(). 3734 */ internalSetBigContentTitle(CharSequence title)3735 protected void internalSetBigContentTitle(CharSequence title) { 3736 mBigContentTitle = title; 3737 } 3738 3739 /** 3740 * Set the first line of text after the detail section in the big form of the template. 3741 */ internalSetSummaryText(CharSequence cs)3742 protected void internalSetSummaryText(CharSequence cs) { 3743 mSummaryText = cs; 3744 mSummaryTextSet = true; 3745 } 3746 setBuilder(Builder builder)3747 public void setBuilder(Builder builder) { 3748 if (mBuilder != builder) { 3749 mBuilder = builder; 3750 if (mBuilder != null) { 3751 mBuilder.setStyle(this); 3752 } 3753 } 3754 } 3755 checkBuilder()3756 protected void checkBuilder() { 3757 if (mBuilder == null) { 3758 throw new IllegalArgumentException("Style requires a valid Builder object"); 3759 } 3760 } 3761 getStandardView(int layoutId)3762 protected RemoteViews getStandardView(int layoutId) { 3763 checkBuilder(); 3764 3765 // Nasty. 3766 CharSequence oldBuilderContentTitle = mBuilder.mContentTitle; 3767 if (mBigContentTitle != null) { 3768 mBuilder.setContentTitle(mBigContentTitle); 3769 } 3770 3771 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId); 3772 3773 mBuilder.mContentTitle = oldBuilderContentTitle; 3774 3775 if (mBigContentTitle != null && mBigContentTitle.equals("")) { 3776 contentView.setViewVisibility(R.id.line1, View.GONE); 3777 } else { 3778 contentView.setViewVisibility(R.id.line1, View.VISIBLE); 3779 } 3780 3781 // The last line defaults to the subtext, but can be replaced by mSummaryText 3782 final CharSequence overflowText = 3783 mSummaryTextSet ? mSummaryText 3784 : mBuilder.mSubText; 3785 if (overflowText != null) { 3786 contentView.setTextViewText(R.id.text, mBuilder.processLegacyText(overflowText)); 3787 contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE); 3788 contentView.setViewVisibility(R.id.line3, View.VISIBLE); 3789 } else { 3790 // Clear text in case we use the line to show the profile badge. 3791 contentView.setTextViewText(R.id.text, ""); 3792 contentView.setViewVisibility(R.id.overflow_divider, View.GONE); 3793 contentView.setViewVisibility(R.id.line3, View.GONE); 3794 } 3795 3796 return contentView; 3797 } 3798 3799 /** 3800 * Changes the padding of the first line such that the big and small content view have the 3801 * same top padding. 3802 * 3803 * @hide 3804 */ applyTopPadding(RemoteViews contentView)3805 protected void applyTopPadding(RemoteViews contentView) { 3806 int topPadding = Builder.calculateTopPadding(mBuilder.mContext, 3807 mBuilder.mHasThreeLines, 3808 mBuilder.mContext.getResources().getConfiguration().fontScale); 3809 contentView.setViewPadding(R.id.line1, 0, topPadding, 0, 0); 3810 } 3811 3812 /** 3813 * @hide 3814 */ addExtras(Bundle extras)3815 public void addExtras(Bundle extras) { 3816 if (mSummaryTextSet) { 3817 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 3818 } 3819 if (mBigContentTitle != null) { 3820 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 3821 } 3822 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 3823 } 3824 3825 /** 3826 * @hide 3827 */ restoreFromExtras(Bundle extras)3828 protected void restoreFromExtras(Bundle extras) { 3829 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 3830 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 3831 mSummaryTextSet = true; 3832 } 3833 if (extras.containsKey(EXTRA_TITLE_BIG)) { 3834 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 3835 } 3836 } 3837 3838 3839 /** 3840 * @hide 3841 */ buildStyled(Notification wip)3842 public Notification buildStyled(Notification wip) { 3843 populateTickerView(wip); 3844 populateContentView(wip); 3845 populateBigContentView(wip); 3846 populateHeadsUpContentView(wip); 3847 return wip; 3848 } 3849 3850 /** 3851 * @hide 3852 */ purgeResources()3853 public void purgeResources() {} 3854 3855 // The following methods are split out so we can re-create notification partially. 3856 /** 3857 * @hide 3858 */ populateTickerView(Notification wip)3859 protected void populateTickerView(Notification wip) {} 3860 /** 3861 * @hide 3862 */ populateContentView(Notification wip)3863 protected void populateContentView(Notification wip) {} 3864 3865 /** 3866 * @hide 3867 */ populateBigContentView(Notification wip)3868 protected void populateBigContentView(Notification wip) {} 3869 3870 /** 3871 * @hide 3872 */ populateHeadsUpContentView(Notification wip)3873 protected void populateHeadsUpContentView(Notification wip) {} 3874 3875 /** 3876 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 3877 * attached to. 3878 * 3879 * @return the fully constructed Notification. 3880 */ build()3881 public Notification build() { 3882 checkBuilder(); 3883 return mBuilder.build(); 3884 } 3885 3886 /** 3887 * @hide 3888 * @return true if the style positions the progress bar on the second line; false if the 3889 * style hides the progress bar 3890 */ hasProgress()3891 protected boolean hasProgress() { 3892 return true; 3893 } 3894 } 3895 3896 /** 3897 * Helper class for generating large-format notifications that include a large image attachment. 3898 * 3899 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 3900 * <pre class="prettyprint"> 3901 * Notification notif = new Notification.Builder(mContext) 3902 * .setContentTitle("New photo from " + sender.toString()) 3903 * .setContentText(subject) 3904 * .setSmallIcon(R.drawable.new_post) 3905 * .setLargeIcon(aBitmap) 3906 * .setStyle(new Notification.BigPictureStyle() 3907 * .bigPicture(aBigBitmap)) 3908 * .build(); 3909 * </pre> 3910 * 3911 * @see Notification#bigContentView 3912 */ 3913 public static class BigPictureStyle extends Style { 3914 private Bitmap mPicture; 3915 private Icon mBigLargeIcon; 3916 private boolean mBigLargeIconSet = false; 3917 BigPictureStyle()3918 public BigPictureStyle() { 3919 } 3920 BigPictureStyle(Builder builder)3921 public BigPictureStyle(Builder builder) { 3922 setBuilder(builder); 3923 } 3924 3925 /** 3926 * Overrides ContentTitle in the big form of the template. 3927 * This defaults to the value passed to setContentTitle(). 3928 */ setBigContentTitle(CharSequence title)3929 public BigPictureStyle setBigContentTitle(CharSequence title) { 3930 internalSetBigContentTitle(safeCharSequence(title)); 3931 return this; 3932 } 3933 3934 /** 3935 * Set the first line of text after the detail section in the big form of the template. 3936 */ setSummaryText(CharSequence cs)3937 public BigPictureStyle setSummaryText(CharSequence cs) { 3938 internalSetSummaryText(safeCharSequence(cs)); 3939 return this; 3940 } 3941 3942 /** 3943 * Provide the bitmap to be used as the payload for the BigPicture notification. 3944 */ bigPicture(Bitmap b)3945 public BigPictureStyle bigPicture(Bitmap b) { 3946 mPicture = b; 3947 return this; 3948 } 3949 3950 /** 3951 * Override the large icon when the big notification is shown. 3952 */ bigLargeIcon(Bitmap b)3953 public BigPictureStyle bigLargeIcon(Bitmap b) { 3954 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 3955 } 3956 3957 /** 3958 * Override the large icon when the big notification is shown. 3959 */ bigLargeIcon(Icon icon)3960 public BigPictureStyle bigLargeIcon(Icon icon) { 3961 mBigLargeIconSet = true; 3962 mBigLargeIcon = icon; 3963 return this; 3964 } 3965 3966 /** 3967 * @hide 3968 */ 3969 @Override purgeResources()3970 public void purgeResources() { 3971 super.purgeResources(); 3972 if (mPicture != null && mPicture.isMutable()) { 3973 mPicture = mPicture.createAshmemBitmap(); 3974 } 3975 if (mBigLargeIcon != null) { 3976 mBigLargeIcon.convertToAshmem(); 3977 } 3978 } 3979 makeBigContentView()3980 private RemoteViews makeBigContentView() { 3981 // Replace mLargeIcon with mBigLargeIcon if mBigLargeIconSet 3982 // This covers the following cases: 3983 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 3984 // mLargeIcon 3985 // 2. !mBigLargeIconSet -> mLargeIcon applies 3986 Icon oldLargeIcon = null; 3987 if (mBigLargeIconSet) { 3988 oldLargeIcon = mBuilder.mLargeIcon; 3989 mBuilder.mLargeIcon = mBigLargeIcon; 3990 } 3991 3992 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource()); 3993 3994 if (mBigLargeIconSet) { 3995 mBuilder.mLargeIcon = oldLargeIcon; 3996 } 3997 3998 contentView.setImageViewBitmap(R.id.big_picture, mPicture); 3999 4000 applyTopPadding(contentView); 4001 4002 boolean twoTextLines = mBuilder.mSubText != null && mBuilder.mContentText != null; 4003 mBuilder.addProfileBadge(contentView, 4004 twoTextLines ? R.id.profile_badge_line2 : R.id.profile_badge_line3); 4005 return contentView; 4006 } 4007 4008 /** 4009 * @hide 4010 */ addExtras(Bundle extras)4011 public void addExtras(Bundle extras) { 4012 super.addExtras(extras); 4013 4014 if (mBigLargeIconSet) { 4015 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 4016 } 4017 extras.putParcelable(EXTRA_PICTURE, mPicture); 4018 } 4019 4020 /** 4021 * @hide 4022 */ 4023 @Override restoreFromExtras(Bundle extras)4024 protected void restoreFromExtras(Bundle extras) { 4025 super.restoreFromExtras(extras); 4026 4027 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 4028 mBigLargeIconSet = true; 4029 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG); 4030 } 4031 mPicture = extras.getParcelable(EXTRA_PICTURE); 4032 } 4033 4034 /** 4035 * @hide 4036 */ 4037 @Override populateBigContentView(Notification wip)4038 public void populateBigContentView(Notification wip) { 4039 mBuilder.setBuilderBigContentView(wip, makeBigContentView()); 4040 } 4041 } 4042 4043 /** 4044 * Helper class for generating large-format notifications that include a lot of text. 4045 * 4046 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 4047 * <pre class="prettyprint"> 4048 * Notification notif = new Notification.Builder(mContext) 4049 * .setContentTitle("New mail from " + sender.toString()) 4050 * .setContentText(subject) 4051 * .setSmallIcon(R.drawable.new_mail) 4052 * .setLargeIcon(aBitmap) 4053 * .setStyle(new Notification.BigTextStyle() 4054 * .bigText(aVeryLongString)) 4055 * .build(); 4056 * </pre> 4057 * 4058 * @see Notification#bigContentView 4059 */ 4060 public static class BigTextStyle extends Style { 4061 4062 private static final int MAX_LINES = 13; 4063 private static final int LINES_CONSUMED_BY_ACTIONS = 3; 4064 private static final int LINES_CONSUMED_BY_SUMMARY = 2; 4065 4066 private CharSequence mBigText; 4067 BigTextStyle()4068 public BigTextStyle() { 4069 } 4070 BigTextStyle(Builder builder)4071 public BigTextStyle(Builder builder) { 4072 setBuilder(builder); 4073 } 4074 4075 /** 4076 * Overrides ContentTitle in the big form of the template. 4077 * This defaults to the value passed to setContentTitle(). 4078 */ setBigContentTitle(CharSequence title)4079 public BigTextStyle setBigContentTitle(CharSequence title) { 4080 internalSetBigContentTitle(safeCharSequence(title)); 4081 return this; 4082 } 4083 4084 /** 4085 * Set the first line of text after the detail section in the big form of the template. 4086 */ setSummaryText(CharSequence cs)4087 public BigTextStyle setSummaryText(CharSequence cs) { 4088 internalSetSummaryText(safeCharSequence(cs)); 4089 return this; 4090 } 4091 4092 /** 4093 * Provide the longer text to be displayed in the big form of the 4094 * template in place of the content text. 4095 */ bigText(CharSequence cs)4096 public BigTextStyle bigText(CharSequence cs) { 4097 mBigText = safeCharSequence(cs); 4098 return this; 4099 } 4100 4101 /** 4102 * @hide 4103 */ addExtras(Bundle extras)4104 public void addExtras(Bundle extras) { 4105 super.addExtras(extras); 4106 4107 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 4108 } 4109 4110 /** 4111 * @hide 4112 */ 4113 @Override restoreFromExtras(Bundle extras)4114 protected void restoreFromExtras(Bundle extras) { 4115 super.restoreFromExtras(extras); 4116 4117 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 4118 } 4119 makeBigContentView()4120 private RemoteViews makeBigContentView() { 4121 4122 // Nasty 4123 CharSequence oldBuilderContentText = mBuilder.mContentText; 4124 mBuilder.mContentText = null; 4125 4126 RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource()); 4127 4128 mBuilder.mContentText = oldBuilderContentText; 4129 4130 contentView.setTextViewText(R.id.big_text, mBuilder.processLegacyText(mBigText)); 4131 contentView.setViewVisibility(R.id.big_text, View.VISIBLE); 4132 contentView.setInt(R.id.big_text, "setMaxLines", calculateMaxLines()); 4133 contentView.setViewVisibility(R.id.text2, View.GONE); 4134 4135 applyTopPadding(contentView); 4136 4137 mBuilder.shrinkLine3Text(contentView); 4138 4139 mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template); 4140 4141 return contentView; 4142 } 4143 calculateMaxLines()4144 private int calculateMaxLines() { 4145 int lineCount = MAX_LINES; 4146 boolean hasActions = mBuilder.mActions.size() > 0; 4147 boolean hasSummary = (mSummaryTextSet ? mSummaryText : mBuilder.mSubText) != null; 4148 if (hasActions) { 4149 lineCount -= LINES_CONSUMED_BY_ACTIONS; 4150 } 4151 if (hasSummary) { 4152 lineCount -= LINES_CONSUMED_BY_SUMMARY; 4153 } 4154 4155 // If we have less top padding at the top, we can fit less lines. 4156 if (!mBuilder.mHasThreeLines) { 4157 lineCount--; 4158 } 4159 return lineCount; 4160 } 4161 4162 /** 4163 * @hide 4164 */ 4165 @Override populateBigContentView(Notification wip)4166 public void populateBigContentView(Notification wip) { 4167 mBuilder.setBuilderBigContentView(wip, makeBigContentView()); 4168 } 4169 } 4170 4171 /** 4172 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 4173 * 4174 * Here's how you'd set the <code>InboxStyle</code> on a notification: 4175 * <pre class="prettyprint"> 4176 * Notification notif = new Notification.Builder(mContext) 4177 * .setContentTitle("5 New mails from " + sender.toString()) 4178 * .setContentText(subject) 4179 * .setSmallIcon(R.drawable.new_mail) 4180 * .setLargeIcon(aBitmap) 4181 * .setStyle(new Notification.InboxStyle() 4182 * .addLine(str1) 4183 * .addLine(str2) 4184 * .setContentTitle("") 4185 * .setSummaryText("+3 more")) 4186 * .build(); 4187 * </pre> 4188 * 4189 * @see Notification#bigContentView 4190 */ 4191 public static class InboxStyle extends Style { 4192 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 4193 InboxStyle()4194 public InboxStyle() { 4195 } 4196 InboxStyle(Builder builder)4197 public InboxStyle(Builder builder) { 4198 setBuilder(builder); 4199 } 4200 4201 /** 4202 * Overrides ContentTitle in the big form of the template. 4203 * This defaults to the value passed to setContentTitle(). 4204 */ setBigContentTitle(CharSequence title)4205 public InboxStyle setBigContentTitle(CharSequence title) { 4206 internalSetBigContentTitle(safeCharSequence(title)); 4207 return this; 4208 } 4209 4210 /** 4211 * Set the first line of text after the detail section in the big form of the template. 4212 */ setSummaryText(CharSequence cs)4213 public InboxStyle setSummaryText(CharSequence cs) { 4214 internalSetSummaryText(safeCharSequence(cs)); 4215 return this; 4216 } 4217 4218 /** 4219 * Append a line to the digest section of the Inbox notification. 4220 */ addLine(CharSequence cs)4221 public InboxStyle addLine(CharSequence cs) { 4222 mTexts.add(safeCharSequence(cs)); 4223 return this; 4224 } 4225 4226 /** 4227 * @hide 4228 */ addExtras(Bundle extras)4229 public void addExtras(Bundle extras) { 4230 super.addExtras(extras); 4231 4232 CharSequence[] a = new CharSequence[mTexts.size()]; 4233 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 4234 } 4235 4236 /** 4237 * @hide 4238 */ 4239 @Override restoreFromExtras(Bundle extras)4240 protected void restoreFromExtras(Bundle extras) { 4241 super.restoreFromExtras(extras); 4242 4243 mTexts.clear(); 4244 if (extras.containsKey(EXTRA_TEXT_LINES)) { 4245 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 4246 } 4247 } 4248 makeBigContentView()4249 private RemoteViews makeBigContentView() { 4250 // Remove the content text so line3 disappears unless you have a summary 4251 4252 // Nasty 4253 CharSequence oldBuilderContentText = mBuilder.mContentText; 4254 mBuilder.mContentText = null; 4255 4256 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource()); 4257 4258 mBuilder.mContentText = oldBuilderContentText; 4259 4260 contentView.setViewVisibility(R.id.text2, View.GONE); 4261 4262 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 4263 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 4264 4265 // Make sure all rows are gone in case we reuse a view. 4266 for (int rowId : rowIds) { 4267 contentView.setViewVisibility(rowId, View.GONE); 4268 } 4269 4270 final boolean largeText = 4271 mBuilder.mContext.getResources().getConfiguration().fontScale > 1f; 4272 final float subTextSize = mBuilder.mContext.getResources().getDimensionPixelSize( 4273 R.dimen.notification_subtext_size); 4274 int i=0; 4275 while (i < mTexts.size() && i < rowIds.length) { 4276 CharSequence str = mTexts.get(i); 4277 if (str != null && !str.equals("")) { 4278 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 4279 contentView.setTextViewText(rowIds[i], mBuilder.processLegacyText(str)); 4280 if (largeText) { 4281 contentView.setTextViewTextSize(rowIds[i], TypedValue.COMPLEX_UNIT_PX, 4282 subTextSize); 4283 } 4284 } 4285 i++; 4286 } 4287 4288 contentView.setViewVisibility(R.id.inbox_end_pad, 4289 mTexts.size() > 0 ? View.VISIBLE : View.GONE); 4290 4291 contentView.setViewVisibility(R.id.inbox_more, 4292 mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE); 4293 4294 applyTopPadding(contentView); 4295 4296 mBuilder.shrinkLine3Text(contentView); 4297 4298 mBuilder.addProfileBadge(contentView, R.id.profile_badge_large_template); 4299 4300 return contentView; 4301 } 4302 4303 /** 4304 * @hide 4305 */ 4306 @Override populateBigContentView(Notification wip)4307 public void populateBigContentView(Notification wip) { 4308 mBuilder.setBuilderBigContentView(wip, makeBigContentView()); 4309 } 4310 } 4311 4312 /** 4313 * Notification style for media playback notifications. 4314 * 4315 * In the expanded form, {@link Notification#bigContentView}, up to 5 4316 * {@link Notification.Action}s specified with 4317 * {@link Notification.Builder#addAction(Action) addAction} will be 4318 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 4319 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 4320 * treated as album artwork. 4321 * 4322 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 4323 * {@link Notification#contentView}; by providing action indices to 4324 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 4325 * in the standard view alongside the usual content. 4326 * 4327 * Notifications created with MediaStyle will have their category set to 4328 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 4329 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 4330 * 4331 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 4332 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 4333 * the System UI can identify this as a notification representing an active media session 4334 * and respond accordingly (by showing album artwork in the lockscreen, for example). 4335 * 4336 * To use this style with your Notification, feed it to 4337 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 4338 * <pre class="prettyprint"> 4339 * Notification noti = new Notification.Builder() 4340 * .setSmallIcon(R.drawable.ic_stat_player) 4341 * .setContentTitle("Track title") 4342 * .setContentText("Artist - Album") 4343 * .setLargeIcon(albumArtBitmap)) 4344 * .setStyle(<b>new Notification.MediaStyle()</b> 4345 * .setMediaSession(mySession)) 4346 * .build(); 4347 * </pre> 4348 * 4349 * @see Notification#bigContentView 4350 */ 4351 public static class MediaStyle extends Style { 4352 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 4353 static final int MAX_MEDIA_BUTTONS = 5; 4354 4355 private int[] mActionsToShowInCompact = null; 4356 private MediaSession.Token mToken; 4357 MediaStyle()4358 public MediaStyle() { 4359 } 4360 MediaStyle(Builder builder)4361 public MediaStyle(Builder builder) { 4362 setBuilder(builder); 4363 } 4364 4365 /** 4366 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 4367 * notification view. 4368 * 4369 * @param actions the indices of the actions to show in the compact notification view 4370 */ setShowActionsInCompactView(int...actions)4371 public MediaStyle setShowActionsInCompactView(int...actions) { 4372 mActionsToShowInCompact = actions; 4373 return this; 4374 } 4375 4376 /** 4377 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 4378 * to provide additional playback information and control to the SystemUI. 4379 */ setMediaSession(MediaSession.Token token)4380 public MediaStyle setMediaSession(MediaSession.Token token) { 4381 mToken = token; 4382 return this; 4383 } 4384 4385 /** 4386 * @hide 4387 */ 4388 @Override buildStyled(Notification wip)4389 public Notification buildStyled(Notification wip) { 4390 super.buildStyled(wip); 4391 if (wip.category == null) { 4392 wip.category = Notification.CATEGORY_TRANSPORT; 4393 } 4394 return wip; 4395 } 4396 4397 /** 4398 * @hide 4399 */ 4400 @Override populateContentView(Notification wip)4401 public void populateContentView(Notification wip) { 4402 mBuilder.setBuilderContentView(wip, makeMediaContentView()); 4403 } 4404 4405 /** 4406 * @hide 4407 */ 4408 @Override populateBigContentView(Notification wip)4409 public void populateBigContentView(Notification wip) { 4410 mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView()); 4411 } 4412 4413 /** @hide */ 4414 @Override addExtras(Bundle extras)4415 public void addExtras(Bundle extras) { 4416 super.addExtras(extras); 4417 4418 if (mToken != null) { 4419 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 4420 } 4421 if (mActionsToShowInCompact != null) { 4422 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 4423 } 4424 } 4425 4426 /** 4427 * @hide 4428 */ 4429 @Override restoreFromExtras(Bundle extras)4430 protected void restoreFromExtras(Bundle extras) { 4431 super.restoreFromExtras(extras); 4432 4433 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 4434 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); 4435 } 4436 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 4437 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 4438 } 4439 } 4440 generateMediaActionButton(Action action)4441 private RemoteViews generateMediaActionButton(Action action) { 4442 final boolean tombstone = (action.actionIntent == null); 4443 RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(), 4444 R.layout.notification_material_media_action); 4445 button.setImageViewIcon(R.id.action0, action.getIcon()); 4446 button.setDrawableParameters(R.id.action0, false, -1, 4447 0xFFFFFFFF, 4448 PorterDuff.Mode.SRC_ATOP, -1); 4449 if (!tombstone) { 4450 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 4451 } 4452 button.setContentDescription(R.id.action0, action.title); 4453 return button; 4454 } 4455 makeMediaContentView()4456 private RemoteViews makeMediaContentView() { 4457 RemoteViews view = mBuilder.applyStandardTemplate( 4458 R.layout.notification_template_material_media, false /* hasProgress */); 4459 4460 final int numActions = mBuilder.mActions.size(); 4461 final int N = mActionsToShowInCompact == null 4462 ? 0 4463 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 4464 if (N > 0) { 4465 view.removeAllViews(com.android.internal.R.id.media_actions); 4466 for (int i = 0; i < N; i++) { 4467 if (i >= numActions) { 4468 throw new IllegalArgumentException(String.format( 4469 "setShowActionsInCompactView: action %d out of bounds (max %d)", 4470 i, numActions - 1)); 4471 } 4472 4473 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 4474 final RemoteViews button = generateMediaActionButton(action); 4475 view.addView(com.android.internal.R.id.media_actions, button); 4476 } 4477 } 4478 styleText(view); 4479 hideRightIcon(view); 4480 return view; 4481 } 4482 makeMediaBigContentView()4483 private RemoteViews makeMediaBigContentView() { 4484 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 4485 RemoteViews big = mBuilder.applyStandardTemplate(getBigLayoutResource(actionCount), 4486 false /* hasProgress */); 4487 4488 if (actionCount > 0) { 4489 big.removeAllViews(com.android.internal.R.id.media_actions); 4490 for (int i = 0; i < actionCount; i++) { 4491 final RemoteViews button = generateMediaActionButton(mBuilder.mActions.get(i)); 4492 big.addView(com.android.internal.R.id.media_actions, button); 4493 } 4494 } 4495 styleText(big); 4496 hideRightIcon(big); 4497 applyTopPadding(big); 4498 big.setViewVisibility(android.R.id.progress, View.GONE); 4499 return big; 4500 } 4501 getBigLayoutResource(int actionCount)4502 private int getBigLayoutResource(int actionCount) { 4503 if (actionCount <= 3) { 4504 return R.layout.notification_template_material_big_media_narrow; 4505 } else { 4506 return R.layout.notification_template_material_big_media; 4507 } 4508 } 4509 hideRightIcon(RemoteViews contentView)4510 private void hideRightIcon(RemoteViews contentView) { 4511 contentView.setViewVisibility(R.id.right_icon, View.GONE); 4512 } 4513 4514 /** 4515 * Applies the special text colors for media notifications to all text views. 4516 */ styleText(RemoteViews contentView)4517 private void styleText(RemoteViews contentView) { 4518 int primaryColor = mBuilder.mContext.getColor( 4519 R.color.notification_media_primary_color); 4520 int secondaryColor = mBuilder.mContext.getColor( 4521 R.color.notification_media_secondary_color); 4522 contentView.setTextColor(R.id.title, primaryColor); 4523 if (mBuilder.showsTimeOrChronometer()) { 4524 if (mBuilder.mUseChronometer) { 4525 contentView.setTextColor(R.id.chronometer, secondaryColor); 4526 } else { 4527 contentView.setTextColor(R.id.time, secondaryColor); 4528 } 4529 } 4530 contentView.setTextColor(R.id.text2, secondaryColor); 4531 contentView.setTextColor(R.id.text, secondaryColor); 4532 contentView.setTextColor(R.id.info, secondaryColor); 4533 } 4534 4535 /** 4536 * @hide 4537 */ 4538 @Override hasProgress()4539 protected boolean hasProgress() { 4540 return false; 4541 } 4542 } 4543 4544 // When adding a new Style subclass here, don't forget to update 4545 // Builder.getNotificationStyleClass. 4546 4547 /** 4548 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 4549 * metadata or change options on a notification builder. 4550 */ 4551 public interface Extender { 4552 /** 4553 * Apply this extender to a notification builder. 4554 * @param builder the builder to be modified. 4555 * @return the build object for chaining. 4556 */ extend(Builder builder)4557 public Builder extend(Builder builder); 4558 } 4559 4560 /** 4561 * Helper class to add wearable extensions to notifications. 4562 * <p class="note"> See 4563 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 4564 * for Android Wear</a> for more information on how to use this class. 4565 * <p> 4566 * To create a notification with wearable extensions: 4567 * <ol> 4568 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 4569 * properties. 4570 * <li>Create a {@link android.app.Notification.WearableExtender}. 4571 * <li>Set wearable-specific properties using the 4572 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 4573 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 4574 * notification. 4575 * <li>Post the notification to the notification system with the 4576 * {@code NotificationManager.notify(...)} methods. 4577 * </ol> 4578 * 4579 * <pre class="prettyprint"> 4580 * Notification notif = new Notification.Builder(mContext) 4581 * .setContentTitle("New mail from " + sender.toString()) 4582 * .setContentText(subject) 4583 * .setSmallIcon(R.drawable.new_mail) 4584 * .extend(new Notification.WearableExtender() 4585 * .setContentIcon(R.drawable.new_mail)) 4586 * .build(); 4587 * NotificationManager notificationManger = 4588 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 4589 * notificationManger.notify(0, notif);</pre> 4590 * 4591 * <p>Wearable extensions can be accessed on an existing notification by using the 4592 * {@code WearableExtender(Notification)} constructor, 4593 * and then using the {@code get} methods to access values. 4594 * 4595 * <pre class="prettyprint"> 4596 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 4597 * notification); 4598 * List<Notification> pages = wearableExtender.getPages();</pre> 4599 */ 4600 public static final class WearableExtender implements Extender { 4601 /** 4602 * Sentinel value for an action index that is unset. 4603 */ 4604 public static final int UNSET_ACTION_INDEX = -1; 4605 4606 /** 4607 * Size value for use with {@link #setCustomSizePreset} to show this notification with 4608 * default sizing. 4609 * <p>For custom display notifications created using {@link #setDisplayIntent}, 4610 * the default is {@link #SIZE_LARGE}. All other notifications size automatically based 4611 * on their content. 4612 */ 4613 public static final int SIZE_DEFAULT = 0; 4614 4615 /** 4616 * Size value for use with {@link #setCustomSizePreset} to show this notification 4617 * with an extra small size. 4618 * <p>This value is only applicable for custom display notifications created using 4619 * {@link #setDisplayIntent}. 4620 */ 4621 public static final int SIZE_XSMALL = 1; 4622 4623 /** 4624 * Size value for use with {@link #setCustomSizePreset} to show this notification 4625 * with a small size. 4626 * <p>This value is only applicable for custom display notifications created using 4627 * {@link #setDisplayIntent}. 4628 */ 4629 public static final int SIZE_SMALL = 2; 4630 4631 /** 4632 * Size value for use with {@link #setCustomSizePreset} to show this notification 4633 * with a medium size. 4634 * <p>This value is only applicable for custom display notifications created using 4635 * {@link #setDisplayIntent}. 4636 */ 4637 public static final int SIZE_MEDIUM = 3; 4638 4639 /** 4640 * Size value for use with {@link #setCustomSizePreset} to show this notification 4641 * with a large size. 4642 * <p>This value is only applicable for custom display notifications created using 4643 * {@link #setDisplayIntent}. 4644 */ 4645 public static final int SIZE_LARGE = 4; 4646 4647 /** 4648 * Size value for use with {@link #setCustomSizePreset} to show this notification 4649 * full screen. 4650 * <p>This value is only applicable for custom display notifications created using 4651 * {@link #setDisplayIntent}. 4652 */ 4653 public static final int SIZE_FULL_SCREEN = 5; 4654 4655 /** 4656 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 4657 * short amount of time when this notification is displayed on the screen. This 4658 * is the default value. 4659 */ 4660 public static final int SCREEN_TIMEOUT_SHORT = 0; 4661 4662 /** 4663 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 4664 * for a longer amount of time when this notification is displayed on the screen. 4665 */ 4666 public static final int SCREEN_TIMEOUT_LONG = -1; 4667 4668 /** Notification extra which contains wearable extensions */ 4669 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 4670 4671 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 4672 private static final String KEY_ACTIONS = "actions"; 4673 private static final String KEY_FLAGS = "flags"; 4674 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 4675 private static final String KEY_PAGES = "pages"; 4676 private static final String KEY_BACKGROUND = "background"; 4677 private static final String KEY_CONTENT_ICON = "contentIcon"; 4678 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 4679 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 4680 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 4681 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 4682 private static final String KEY_GRAVITY = "gravity"; 4683 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 4684 4685 // Flags bitwise-ored to mFlags 4686 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 4687 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 4688 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 4689 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 4690 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 4691 4692 // Default value for flags integer 4693 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 4694 4695 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 4696 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 4697 4698 private ArrayList<Action> mActions = new ArrayList<Action>(); 4699 private int mFlags = DEFAULT_FLAGS; 4700 private PendingIntent mDisplayIntent; 4701 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 4702 private Bitmap mBackground; 4703 private int mContentIcon; 4704 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 4705 private int mContentActionIndex = UNSET_ACTION_INDEX; 4706 private int mCustomSizePreset = SIZE_DEFAULT; 4707 private int mCustomContentHeight; 4708 private int mGravity = DEFAULT_GRAVITY; 4709 private int mHintScreenTimeout; 4710 4711 /** 4712 * Create a {@link android.app.Notification.WearableExtender} with default 4713 * options. 4714 */ WearableExtender()4715 public WearableExtender() { 4716 } 4717 WearableExtender(Notification notif)4718 public WearableExtender(Notification notif) { 4719 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 4720 if (wearableBundle != null) { 4721 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 4722 if (actions != null) { 4723 mActions.addAll(actions); 4724 } 4725 4726 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 4727 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 4728 4729 Notification[] pages = getNotificationArrayFromBundle( 4730 wearableBundle, KEY_PAGES); 4731 if (pages != null) { 4732 Collections.addAll(mPages, pages); 4733 } 4734 4735 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 4736 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 4737 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 4738 DEFAULT_CONTENT_ICON_GRAVITY); 4739 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 4740 UNSET_ACTION_INDEX); 4741 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 4742 SIZE_DEFAULT); 4743 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 4744 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 4745 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 4746 } 4747 } 4748 4749 /** 4750 * Apply wearable extensions to a notification that is being built. This is typically 4751 * called by the {@link android.app.Notification.Builder#extend} method of 4752 * {@link android.app.Notification.Builder}. 4753 */ 4754 @Override extend(Notification.Builder builder)4755 public Notification.Builder extend(Notification.Builder builder) { 4756 Bundle wearableBundle = new Bundle(); 4757 4758 if (!mActions.isEmpty()) { 4759 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 4760 } 4761 if (mFlags != DEFAULT_FLAGS) { 4762 wearableBundle.putInt(KEY_FLAGS, mFlags); 4763 } 4764 if (mDisplayIntent != null) { 4765 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 4766 } 4767 if (!mPages.isEmpty()) { 4768 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 4769 new Notification[mPages.size()])); 4770 } 4771 if (mBackground != null) { 4772 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 4773 } 4774 if (mContentIcon != 0) { 4775 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 4776 } 4777 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 4778 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 4779 } 4780 if (mContentActionIndex != UNSET_ACTION_INDEX) { 4781 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 4782 mContentActionIndex); 4783 } 4784 if (mCustomSizePreset != SIZE_DEFAULT) { 4785 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 4786 } 4787 if (mCustomContentHeight != 0) { 4788 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 4789 } 4790 if (mGravity != DEFAULT_GRAVITY) { 4791 wearableBundle.putInt(KEY_GRAVITY, mGravity); 4792 } 4793 if (mHintScreenTimeout != 0) { 4794 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 4795 } 4796 4797 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 4798 return builder; 4799 } 4800 4801 @Override clone()4802 public WearableExtender clone() { 4803 WearableExtender that = new WearableExtender(); 4804 that.mActions = new ArrayList<Action>(this.mActions); 4805 that.mFlags = this.mFlags; 4806 that.mDisplayIntent = this.mDisplayIntent; 4807 that.mPages = new ArrayList<Notification>(this.mPages); 4808 that.mBackground = this.mBackground; 4809 that.mContentIcon = this.mContentIcon; 4810 that.mContentIconGravity = this.mContentIconGravity; 4811 that.mContentActionIndex = this.mContentActionIndex; 4812 that.mCustomSizePreset = this.mCustomSizePreset; 4813 that.mCustomContentHeight = this.mCustomContentHeight; 4814 that.mGravity = this.mGravity; 4815 that.mHintScreenTimeout = this.mHintScreenTimeout; 4816 return that; 4817 } 4818 4819 /** 4820 * Add a wearable action to this notification. 4821 * 4822 * <p>When wearable actions are added using this method, the set of actions that 4823 * show on a wearable device splits from devices that only show actions added 4824 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 4825 * of which actions display on different devices. 4826 * 4827 * @param action the action to add to this notification 4828 * @return this object for method chaining 4829 * @see android.app.Notification.Action 4830 */ addAction(Action action)4831 public WearableExtender addAction(Action action) { 4832 mActions.add(action); 4833 return this; 4834 } 4835 4836 /** 4837 * Adds wearable actions to this notification. 4838 * 4839 * <p>When wearable actions are added using this method, the set of actions that 4840 * show on a wearable device splits from devices that only show actions added 4841 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 4842 * of which actions display on different devices. 4843 * 4844 * @param actions the actions to add to this notification 4845 * @return this object for method chaining 4846 * @see android.app.Notification.Action 4847 */ addActions(List<Action> actions)4848 public WearableExtender addActions(List<Action> actions) { 4849 mActions.addAll(actions); 4850 return this; 4851 } 4852 4853 /** 4854 * Clear all wearable actions present on this builder. 4855 * @return this object for method chaining. 4856 * @see #addAction 4857 */ clearActions()4858 public WearableExtender clearActions() { 4859 mActions.clear(); 4860 return this; 4861 } 4862 4863 /** 4864 * Get the wearable actions present on this notification. 4865 */ getActions()4866 public List<Action> getActions() { 4867 return mActions; 4868 } 4869 4870 /** 4871 * Set an intent to launch inside of an activity view when displaying 4872 * this notification. The {@link PendingIntent} provided should be for an activity. 4873 * 4874 * <pre class="prettyprint"> 4875 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 4876 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 4877 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); 4878 * Notification notif = new Notification.Builder(context) 4879 * .extend(new Notification.WearableExtender() 4880 * .setDisplayIntent(displayPendingIntent) 4881 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 4882 * .build();</pre> 4883 * 4884 * <p>The activity to launch needs to allow embedding, must be exported, and 4885 * should have an empty task affinity. It is also recommended to use the device 4886 * default light theme. 4887 * 4888 * <p>Example AndroidManifest.xml entry: 4889 * <pre class="prettyprint"> 4890 * <activity android:name="com.example.MyDisplayActivity" 4891 * android:exported="true" 4892 * android:allowEmbedded="true" 4893 * android:taskAffinity="" 4894 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 4895 * 4896 * @param intent the {@link PendingIntent} for an activity 4897 * @return this object for method chaining 4898 * @see android.app.Notification.WearableExtender#getDisplayIntent 4899 */ setDisplayIntent(PendingIntent intent)4900 public WearableExtender setDisplayIntent(PendingIntent intent) { 4901 mDisplayIntent = intent; 4902 return this; 4903 } 4904 4905 /** 4906 * Get the intent to launch inside of an activity view when displaying this 4907 * notification. This {@code PendingIntent} should be for an activity. 4908 */ getDisplayIntent()4909 public PendingIntent getDisplayIntent() { 4910 return mDisplayIntent; 4911 } 4912 4913 /** 4914 * Add an additional page of content to display with this notification. The current 4915 * notification forms the first page, and pages added using this function form 4916 * subsequent pages. This field can be used to separate a notification into multiple 4917 * sections. 4918 * 4919 * @param page the notification to add as another page 4920 * @return this object for method chaining 4921 * @see android.app.Notification.WearableExtender#getPages 4922 */ addPage(Notification page)4923 public WearableExtender addPage(Notification page) { 4924 mPages.add(page); 4925 return this; 4926 } 4927 4928 /** 4929 * Add additional pages of content to display with this notification. The current 4930 * notification forms the first page, and pages added using this function form 4931 * subsequent pages. This field can be used to separate a notification into multiple 4932 * sections. 4933 * 4934 * @param pages a list of notifications 4935 * @return this object for method chaining 4936 * @see android.app.Notification.WearableExtender#getPages 4937 */ addPages(List<Notification> pages)4938 public WearableExtender addPages(List<Notification> pages) { 4939 mPages.addAll(pages); 4940 return this; 4941 } 4942 4943 /** 4944 * Clear all additional pages present on this builder. 4945 * @return this object for method chaining. 4946 * @see #addPage 4947 */ clearPages()4948 public WearableExtender clearPages() { 4949 mPages.clear(); 4950 return this; 4951 } 4952 4953 /** 4954 * Get the array of additional pages of content for displaying this notification. The 4955 * current notification forms the first page, and elements within this array form 4956 * subsequent pages. This field can be used to separate a notification into multiple 4957 * sections. 4958 * @return the pages for this notification 4959 */ getPages()4960 public List<Notification> getPages() { 4961 return mPages; 4962 } 4963 4964 /** 4965 * Set a background image to be displayed behind the notification content. 4966 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 4967 * will work with any notification style. 4968 * 4969 * @param background the background bitmap 4970 * @return this object for method chaining 4971 * @see android.app.Notification.WearableExtender#getBackground 4972 */ setBackground(Bitmap background)4973 public WearableExtender setBackground(Bitmap background) { 4974 mBackground = background; 4975 return this; 4976 } 4977 4978 /** 4979 * Get a background image to be displayed behind the notification content. 4980 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 4981 * will work with any notification style. 4982 * 4983 * @return the background image 4984 * @see android.app.Notification.WearableExtender#setBackground 4985 */ getBackground()4986 public Bitmap getBackground() { 4987 return mBackground; 4988 } 4989 4990 /** 4991 * Set an icon that goes with the content of this notification. 4992 */ setContentIcon(int icon)4993 public WearableExtender setContentIcon(int icon) { 4994 mContentIcon = icon; 4995 return this; 4996 } 4997 4998 /** 4999 * Get an icon that goes with the content of this notification. 5000 */ getContentIcon()5001 public int getContentIcon() { 5002 return mContentIcon; 5003 } 5004 5005 /** 5006 * Set the gravity that the content icon should have within the notification display. 5007 * Supported values include {@link android.view.Gravity#START} and 5008 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 5009 * @see #setContentIcon 5010 */ setContentIconGravity(int contentIconGravity)5011 public WearableExtender setContentIconGravity(int contentIconGravity) { 5012 mContentIconGravity = contentIconGravity; 5013 return this; 5014 } 5015 5016 /** 5017 * Get the gravity that the content icon should have within the notification display. 5018 * Supported values include {@link android.view.Gravity#START} and 5019 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 5020 * @see #getContentIcon 5021 */ getContentIconGravity()5022 public int getContentIconGravity() { 5023 return mContentIconGravity; 5024 } 5025 5026 /** 5027 * Set an action from this notification's actions to be clickable with the content of 5028 * this notification. This action will no longer display separately from the 5029 * notification's content. 5030 * 5031 * <p>For notifications with multiple pages, child pages can also have content actions 5032 * set, although the list of available actions comes from the main notification and not 5033 * from the child page's notification. 5034 * 5035 * @param actionIndex The index of the action to hoist onto the current notification page. 5036 * If wearable actions were added to the main notification, this index 5037 * will apply to that list, otherwise it will apply to the regular 5038 * actions list. 5039 */ setContentAction(int actionIndex)5040 public WearableExtender setContentAction(int actionIndex) { 5041 mContentActionIndex = actionIndex; 5042 return this; 5043 } 5044 5045 /** 5046 * Get the index of the notification action, if any, that was specified as being clickable 5047 * with the content of this notification. This action will no longer display separately 5048 * from the notification's content. 5049 * 5050 * <p>For notifications with multiple pages, child pages can also have content actions 5051 * set, although the list of available actions comes from the main notification and not 5052 * from the child page's notification. 5053 * 5054 * <p>If wearable specific actions were added to the main notification, this index will 5055 * apply to that list, otherwise it will apply to the regular actions list. 5056 * 5057 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 5058 */ getContentAction()5059 public int getContentAction() { 5060 return mContentActionIndex; 5061 } 5062 5063 /** 5064 * Set the gravity that this notification should have within the available viewport space. 5065 * Supported values include {@link android.view.Gravity#TOP}, 5066 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 5067 * The default value is {@link android.view.Gravity#BOTTOM}. 5068 */ setGravity(int gravity)5069 public WearableExtender setGravity(int gravity) { 5070 mGravity = gravity; 5071 return this; 5072 } 5073 5074 /** 5075 * Get the gravity that this notification should have within the available viewport space. 5076 * Supported values include {@link android.view.Gravity#TOP}, 5077 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 5078 * The default value is {@link android.view.Gravity#BOTTOM}. 5079 */ getGravity()5080 public int getGravity() { 5081 return mGravity; 5082 } 5083 5084 /** 5085 * Set the custom size preset for the display of this notification out of the available 5086 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 5087 * {@link #SIZE_LARGE}. 5088 * <p>Some custom size presets are only applicable for custom display notifications created 5089 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 5090 * documentation for the preset in question. See also 5091 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 5092 */ setCustomSizePreset(int sizePreset)5093 public WearableExtender setCustomSizePreset(int sizePreset) { 5094 mCustomSizePreset = sizePreset; 5095 return this; 5096 } 5097 5098 /** 5099 * Get the custom size preset for the display of this notification out of the available 5100 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 5101 * {@link #SIZE_LARGE}. 5102 * <p>Some custom size presets are only applicable for custom display notifications created 5103 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 5104 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 5105 */ getCustomSizePreset()5106 public int getCustomSizePreset() { 5107 return mCustomSizePreset; 5108 } 5109 5110 /** 5111 * Set the custom height in pixels for the display of this notification's content. 5112 * <p>This option is only available for custom display notifications created 5113 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 5114 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 5115 * {@link #getCustomContentHeight}. 5116 */ setCustomContentHeight(int height)5117 public WearableExtender setCustomContentHeight(int height) { 5118 mCustomContentHeight = height; 5119 return this; 5120 } 5121 5122 /** 5123 * Get the custom height in pixels for the display of this notification's content. 5124 * <p>This option is only available for custom display notifications created 5125 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 5126 * {@link #setCustomContentHeight}. 5127 */ getCustomContentHeight()5128 public int getCustomContentHeight() { 5129 return mCustomContentHeight; 5130 } 5131 5132 /** 5133 * Set whether the scrolling position for the contents of this notification should start 5134 * at the bottom of the contents instead of the top when the contents are too long to 5135 * display within the screen. Default is false (start scroll at the top). 5136 */ setStartScrollBottom(boolean startScrollBottom)5137 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 5138 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 5139 return this; 5140 } 5141 5142 /** 5143 * Get whether the scrolling position for the contents of this notification should start 5144 * at the bottom of the contents instead of the top when the contents are too long to 5145 * display within the screen. Default is false (start scroll at the top). 5146 */ getStartScrollBottom()5147 public boolean getStartScrollBottom() { 5148 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 5149 } 5150 5151 /** 5152 * Set whether the content intent is available when the wearable device is not connected 5153 * to a companion device. The user can still trigger this intent when the wearable device 5154 * is offline, but a visual hint will indicate that the content intent may not be available. 5155 * Defaults to true. 5156 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)5157 public WearableExtender setContentIntentAvailableOffline( 5158 boolean contentIntentAvailableOffline) { 5159 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 5160 return this; 5161 } 5162 5163 /** 5164 * Get whether the content intent is available when the wearable device is not connected 5165 * to a companion device. The user can still trigger this intent when the wearable device 5166 * is offline, but a visual hint will indicate that the content intent may not be available. 5167 * Defaults to true. 5168 */ getContentIntentAvailableOffline()5169 public boolean getContentIntentAvailableOffline() { 5170 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 5171 } 5172 5173 /** 5174 * Set a hint that this notification's icon should not be displayed. 5175 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 5176 * @return this object for method chaining 5177 */ setHintHideIcon(boolean hintHideIcon)5178 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 5179 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 5180 return this; 5181 } 5182 5183 /** 5184 * Get a hint that this notification's icon should not be displayed. 5185 * @return {@code true} if this icon should not be displayed, false otherwise. 5186 * The default value is {@code false} if this was never set. 5187 */ getHintHideIcon()5188 public boolean getHintHideIcon() { 5189 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 5190 } 5191 5192 /** 5193 * Set a visual hint that only the background image of this notification should be 5194 * displayed, and other semantic content should be hidden. This hint is only applicable 5195 * to sub-pages added using {@link #addPage}. 5196 */ setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)5197 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 5198 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 5199 return this; 5200 } 5201 5202 /** 5203 * Get a visual hint that only the background image of this notification should be 5204 * displayed, and other semantic content should be hidden. This hint is only applicable 5205 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 5206 */ getHintShowBackgroundOnly()5207 public boolean getHintShowBackgroundOnly() { 5208 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 5209 } 5210 5211 /** 5212 * Set a hint that this notification's background should not be clipped if possible, 5213 * and should instead be resized to fully display on the screen, retaining the aspect 5214 * ratio of the image. This can be useful for images like barcodes or qr codes. 5215 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 5216 * @return this object for method chaining 5217 */ setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)5218 public WearableExtender setHintAvoidBackgroundClipping( 5219 boolean hintAvoidBackgroundClipping) { 5220 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 5221 return this; 5222 } 5223 5224 /** 5225 * Get a hint that this notification's background should not be clipped if possible, 5226 * and should instead be resized to fully display on the screen, retaining the aspect 5227 * ratio of the image. This can be useful for images like barcodes or qr codes. 5228 * @return {@code true} if it's ok if the background is clipped on the screen, false 5229 * otherwise. The default value is {@code false} if this was never set. 5230 */ getHintAvoidBackgroundClipping()5231 public boolean getHintAvoidBackgroundClipping() { 5232 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 5233 } 5234 5235 /** 5236 * Set a hint that the screen should remain on for at least this duration when 5237 * this notification is displayed on the screen. 5238 * @param timeout The requested screen timeout in milliseconds. Can also be either 5239 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 5240 * @return this object for method chaining 5241 */ setHintScreenTimeout(int timeout)5242 public WearableExtender setHintScreenTimeout(int timeout) { 5243 mHintScreenTimeout = timeout; 5244 return this; 5245 } 5246 5247 /** 5248 * Get the duration, in milliseconds, that the screen should remain on for 5249 * when this notification is displayed. 5250 * @return the duration in milliseconds if > 0, or either one of the sentinel values 5251 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 5252 */ getHintScreenTimeout()5253 public int getHintScreenTimeout() { 5254 return mHintScreenTimeout; 5255 } 5256 setFlag(int mask, boolean value)5257 private void setFlag(int mask, boolean value) { 5258 if (value) { 5259 mFlags |= mask; 5260 } else { 5261 mFlags &= ~mask; 5262 } 5263 } 5264 } 5265 5266 /** 5267 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 5268 * with car extensions: 5269 * 5270 * <ol> 5271 * <li>Create an {@link Notification.Builder}, setting any desired 5272 * properties. 5273 * <li>Create a {@link CarExtender}. 5274 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 5275 * {@link CarExtender}. 5276 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 5277 * to apply the extensions to a notification. 5278 * </ol> 5279 * 5280 * <pre class="prettyprint"> 5281 * Notification notification = new Notification.Builder(context) 5282 * ... 5283 * .extend(new CarExtender() 5284 * .set*(...)) 5285 * .build(); 5286 * </pre> 5287 * 5288 * <p>Car extensions can be accessed on an existing notification by using the 5289 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 5290 * to access values. 5291 */ 5292 public static final class CarExtender implements Extender { 5293 private static final String TAG = "CarExtender"; 5294 5295 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 5296 private static final String EXTRA_LARGE_ICON = "large_icon"; 5297 private static final String EXTRA_CONVERSATION = "car_conversation"; 5298 private static final String EXTRA_COLOR = "app_color"; 5299 5300 private Bitmap mLargeIcon; 5301 private UnreadConversation mUnreadConversation; 5302 private int mColor = Notification.COLOR_DEFAULT; 5303 5304 /** 5305 * Create a {@link CarExtender} with default options. 5306 */ CarExtender()5307 public CarExtender() { 5308 } 5309 5310 /** 5311 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 5312 * 5313 * @param notif The notification from which to copy options. 5314 */ CarExtender(Notification notif)5315 public CarExtender(Notification notif) { 5316 Bundle carBundle = notif.extras == null ? 5317 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 5318 if (carBundle != null) { 5319 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 5320 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 5321 5322 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 5323 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 5324 } 5325 } 5326 5327 /** 5328 * Apply car extensions to a notification that is being built. This is typically called by 5329 * the {@link Notification.Builder#extend(Notification.Extender)} 5330 * method of {@link Notification.Builder}. 5331 */ 5332 @Override extend(Notification.Builder builder)5333 public Notification.Builder extend(Notification.Builder builder) { 5334 Bundle carExtensions = new Bundle(); 5335 5336 if (mLargeIcon != null) { 5337 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 5338 } 5339 if (mColor != Notification.COLOR_DEFAULT) { 5340 carExtensions.putInt(EXTRA_COLOR, mColor); 5341 } 5342 5343 if (mUnreadConversation != null) { 5344 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 5345 carExtensions.putBundle(EXTRA_CONVERSATION, b); 5346 } 5347 5348 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 5349 return builder; 5350 } 5351 5352 /** 5353 * Sets the accent color to use when Android Auto presents the notification. 5354 * 5355 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 5356 * to accent the displayed notification. However, not all colors are acceptable in an 5357 * automotive setting. This method can be used to override the color provided in the 5358 * notification in such a situation. 5359 */ setColor(@olorInt int color)5360 public CarExtender setColor(@ColorInt int color) { 5361 mColor = color; 5362 return this; 5363 } 5364 5365 /** 5366 * Gets the accent color. 5367 * 5368 * @see setColor 5369 */ 5370 @ColorInt getColor()5371 public int getColor() { 5372 return mColor; 5373 } 5374 5375 /** 5376 * Sets the large icon of the car notification. 5377 * 5378 * If no large icon is set in the extender, Android Auto will display the icon 5379 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 5380 * 5381 * @param largeIcon The large icon to use in the car notification. 5382 * @return This object for method chaining. 5383 */ setLargeIcon(Bitmap largeIcon)5384 public CarExtender setLargeIcon(Bitmap largeIcon) { 5385 mLargeIcon = largeIcon; 5386 return this; 5387 } 5388 5389 /** 5390 * Gets the large icon used in this car notification, or null if no icon has been set. 5391 * 5392 * @return The large icon for the car notification. 5393 * @see CarExtender#setLargeIcon 5394 */ getLargeIcon()5395 public Bitmap getLargeIcon() { 5396 return mLargeIcon; 5397 } 5398 5399 /** 5400 * Sets the unread conversation in a message notification. 5401 * 5402 * @param unreadConversation The unread part of the conversation this notification conveys. 5403 * @return This object for method chaining. 5404 */ setUnreadConversation(UnreadConversation unreadConversation)5405 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 5406 mUnreadConversation = unreadConversation; 5407 return this; 5408 } 5409 5410 /** 5411 * Returns the unread conversation conveyed by this notification. 5412 * @see #setUnreadConversation(UnreadConversation) 5413 */ getUnreadConversation()5414 public UnreadConversation getUnreadConversation() { 5415 return mUnreadConversation; 5416 } 5417 5418 /** 5419 * A class which holds the unread messages from a conversation. 5420 */ 5421 public static class UnreadConversation { 5422 private static final String KEY_AUTHOR = "author"; 5423 private static final String KEY_TEXT = "text"; 5424 private static final String KEY_MESSAGES = "messages"; 5425 private static final String KEY_REMOTE_INPUT = "remote_input"; 5426 private static final String KEY_ON_REPLY = "on_reply"; 5427 private static final String KEY_ON_READ = "on_read"; 5428 private static final String KEY_PARTICIPANTS = "participants"; 5429 private static final String KEY_TIMESTAMP = "timestamp"; 5430 5431 private final String[] mMessages; 5432 private final RemoteInput mRemoteInput; 5433 private final PendingIntent mReplyPendingIntent; 5434 private final PendingIntent mReadPendingIntent; 5435 private final String[] mParticipants; 5436 private final long mLatestTimestamp; 5437 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)5438 UnreadConversation(String[] messages, RemoteInput remoteInput, 5439 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 5440 String[] participants, long latestTimestamp) { 5441 mMessages = messages; 5442 mRemoteInput = remoteInput; 5443 mReadPendingIntent = readPendingIntent; 5444 mReplyPendingIntent = replyPendingIntent; 5445 mParticipants = participants; 5446 mLatestTimestamp = latestTimestamp; 5447 } 5448 5449 /** 5450 * Gets the list of messages conveyed by this notification. 5451 */ getMessages()5452 public String[] getMessages() { 5453 return mMessages; 5454 } 5455 5456 /** 5457 * Gets the remote input that will be used to convey the response to a message list, or 5458 * null if no such remote input exists. 5459 */ getRemoteInput()5460 public RemoteInput getRemoteInput() { 5461 return mRemoteInput; 5462 } 5463 5464 /** 5465 * Gets the pending intent that will be triggered when the user replies to this 5466 * notification. 5467 */ getReplyPendingIntent()5468 public PendingIntent getReplyPendingIntent() { 5469 return mReplyPendingIntent; 5470 } 5471 5472 /** 5473 * Gets the pending intent that Android Auto will send after it reads aloud all messages 5474 * in this object's message list. 5475 */ getReadPendingIntent()5476 public PendingIntent getReadPendingIntent() { 5477 return mReadPendingIntent; 5478 } 5479 5480 /** 5481 * Gets the participants in the conversation. 5482 */ getParticipants()5483 public String[] getParticipants() { 5484 return mParticipants; 5485 } 5486 5487 /** 5488 * Gets the firs participant in the conversation. 5489 */ getParticipant()5490 public String getParticipant() { 5491 return mParticipants.length > 0 ? mParticipants[0] : null; 5492 } 5493 5494 /** 5495 * Gets the timestamp of the conversation. 5496 */ getLatestTimestamp()5497 public long getLatestTimestamp() { 5498 return mLatestTimestamp; 5499 } 5500 getBundleForUnreadConversation()5501 Bundle getBundleForUnreadConversation() { 5502 Bundle b = new Bundle(); 5503 String author = null; 5504 if (mParticipants != null && mParticipants.length > 1) { 5505 author = mParticipants[0]; 5506 } 5507 Parcelable[] messages = new Parcelable[mMessages.length]; 5508 for (int i = 0; i < messages.length; i++) { 5509 Bundle m = new Bundle(); 5510 m.putString(KEY_TEXT, mMessages[i]); 5511 m.putString(KEY_AUTHOR, author); 5512 messages[i] = m; 5513 } 5514 b.putParcelableArray(KEY_MESSAGES, messages); 5515 if (mRemoteInput != null) { 5516 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 5517 } 5518 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 5519 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 5520 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 5521 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 5522 return b; 5523 } 5524 getUnreadConversationFromBundle(Bundle b)5525 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 5526 if (b == null) { 5527 return null; 5528 } 5529 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 5530 String[] messages = null; 5531 if (parcelableMessages != null) { 5532 String[] tmp = new String[parcelableMessages.length]; 5533 boolean success = true; 5534 for (int i = 0; i < tmp.length; i++) { 5535 if (!(parcelableMessages[i] instanceof Bundle)) { 5536 success = false; 5537 break; 5538 } 5539 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 5540 if (tmp[i] == null) { 5541 success = false; 5542 break; 5543 } 5544 } 5545 if (success) { 5546 messages = tmp; 5547 } else { 5548 return null; 5549 } 5550 } 5551 5552 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 5553 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 5554 5555 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 5556 5557 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 5558 if (participants == null || participants.length != 1) { 5559 return null; 5560 } 5561 5562 return new UnreadConversation(messages, 5563 remoteInput, 5564 onReply, 5565 onRead, 5566 participants, b.getLong(KEY_TIMESTAMP)); 5567 } 5568 }; 5569 5570 /** 5571 * Builder class for {@link CarExtender.UnreadConversation} objects. 5572 */ 5573 public static class Builder { 5574 private final List<String> mMessages = new ArrayList<String>(); 5575 private final String mParticipant; 5576 private RemoteInput mRemoteInput; 5577 private PendingIntent mReadPendingIntent; 5578 private PendingIntent mReplyPendingIntent; 5579 private long mLatestTimestamp; 5580 5581 /** 5582 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 5583 * 5584 * @param name The name of the other participant in the conversation. 5585 */ Builder(String name)5586 public Builder(String name) { 5587 mParticipant = name; 5588 } 5589 5590 /** 5591 * Appends a new unread message to the list of messages for this conversation. 5592 * 5593 * The messages should be added from oldest to newest. 5594 * 5595 * @param message The text of the new unread message. 5596 * @return This object for method chaining. 5597 */ addMessage(String message)5598 public Builder addMessage(String message) { 5599 mMessages.add(message); 5600 return this; 5601 } 5602 5603 /** 5604 * Sets the pending intent and remote input which will convey the reply to this 5605 * notification. 5606 * 5607 * @param pendingIntent The pending intent which will be triggered on a reply. 5608 * @param remoteInput The remote input parcelable which will carry the reply. 5609 * @return This object for method chaining. 5610 * 5611 * @see CarExtender.UnreadConversation#getRemoteInput 5612 * @see CarExtender.UnreadConversation#getReplyPendingIntent 5613 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)5614 public Builder setReplyAction( 5615 PendingIntent pendingIntent, RemoteInput remoteInput) { 5616 mRemoteInput = remoteInput; 5617 mReplyPendingIntent = pendingIntent; 5618 5619 return this; 5620 } 5621 5622 /** 5623 * Sets the pending intent that will be sent once the messages in this notification 5624 * are read. 5625 * 5626 * @param pendingIntent The pending intent to use. 5627 * @return This object for method chaining. 5628 */ setReadPendingIntent(PendingIntent pendingIntent)5629 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 5630 mReadPendingIntent = pendingIntent; 5631 return this; 5632 } 5633 5634 /** 5635 * Sets the timestamp of the most recent message in an unread conversation. 5636 * 5637 * If a messaging notification has been posted by your application and has not 5638 * yet been cancelled, posting a later notification with the same id and tag 5639 * but without a newer timestamp may result in Android Auto not displaying a 5640 * heads up notification for the later notification. 5641 * 5642 * @param timestamp The timestamp of the most recent message in the conversation. 5643 * @return This object for method chaining. 5644 */ setLatestTimestamp(long timestamp)5645 public Builder setLatestTimestamp(long timestamp) { 5646 mLatestTimestamp = timestamp; 5647 return this; 5648 } 5649 5650 /** 5651 * Builds a new unread conversation object. 5652 * 5653 * @return The new unread conversation object. 5654 */ build()5655 public UnreadConversation build() { 5656 String[] messages = mMessages.toArray(new String[mMessages.size()]); 5657 String[] participants = { mParticipant }; 5658 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 5659 mReadPendingIntent, participants, mLatestTimestamp); 5660 } 5661 } 5662 } 5663 5664 /** 5665 * Get an array of Notification objects from a parcelable array bundle field. 5666 * Update the bundle to have a typed array so fetches in the future don't need 5667 * to do an array copy. 5668 */ getNotificationArrayFromBundle(Bundle bundle, String key)5669 private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { 5670 Parcelable[] array = bundle.getParcelableArray(key); 5671 if (array instanceof Notification[] || array == null) { 5672 return (Notification[]) array; 5673 } 5674 Notification[] typedArray = Arrays.copyOf(array, array.length, 5675 Notification[].class); 5676 bundle.putParcelableArray(key, typedArray); 5677 return typedArray; 5678 } 5679 5680 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)5681 public BuilderRemoteViews(Parcel parcel) { 5682 super(parcel); 5683 } 5684 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)5685 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 5686 super(appInfo, layoutId); 5687 } 5688 5689 @Override clone()5690 public BuilderRemoteViews clone() { 5691 Parcel p = Parcel.obtain(); 5692 writeToParcel(p, 0); 5693 p.setDataPosition(0); 5694 BuilderRemoteViews brv = new BuilderRemoteViews(p); 5695 p.recycle(); 5696 return brv; 5697 } 5698 } 5699 } 5700