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 notificationfor example, to redact some portionssee 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 */ 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 */ 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 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 949 public Action(int icon, CharSequence title, PendingIntent intent) { 950 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null); 951 } 952 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 */ 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 */ 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 */ 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 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 */ 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 */ 1024 public Builder(Action action) { 1025 this(action.getIcon(), action.title, action.actionIntent, new Bundle(action.mExtras), 1026 action.getRemoteInputs()); 1027 } 1028 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 1110 public int describeContents() { 1111 return 0; 1112 } 1113 @Override 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 1266 public boolean isAvailableOffline() { 1267 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 1268 } 1269 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 1365 public Notification() 1366 { 1367 this.when = System.currentTimeMillis(); 1368 this.priority = PRIORITY_DEFAULT; 1369 } 1370 1371 /** 1372 * @hide 1373 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 1632 public int describeContents() { 1633 return 0; 1634 } 1635 1636 /** 1637 * Flatten this notification into a parcel. 1638 */ 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 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 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 */ 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 */ 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 */ 1943 public Icon getSmallIcon() { 1944 return mSmallIcon; 1945 } 1946 1947 /** 1948 * Used when notifying to clean up legacy small icons. 1949 * @hide 1950 */ 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 */ 1960 public Icon getLargeIcon() { 1961 return mLargeIcon; 1962 } 1963 1964 /** 1965 * @hide 1966 */ 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 */ 1976 public boolean isGroupSummary() { 1977 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 1978 } 1979 1980 /** 1981 * @hide 1982 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 3144 private RemoteViews makeContentView() { 3145 if (mContentView != null) { 3146 return mContentView; 3147 } else { 3148 return applyStandardTemplate(getBaseLayoutResource()); 3149 } 3150 } 3151 3152 private RemoteViews makeTickerView() { 3153 if (mTickerView != null) { 3154 return mTickerView; 3155 } 3156 return null; // tickers are not created by default anymore 3157 } 3158 3159 private RemoteViews makeBigContentView() { 3160 if (mActions.size() == 0) return null; 3161 3162 return applyStandardTemplateWithActions(getBigBaseLayoutResource()); 3163 } 3164 3165 private RemoteViews makeHeadsUpContentView() { 3166 if (mActions.size() == 0) return null; 3167 3168 return applyStandardTemplateWithActions(getBigBaseLayoutResource()); 3169 } 3170 3171 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 */ 3192 private boolean isLegacy() { 3193 return mColorUtil != null; 3194 } 3195 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 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 */ 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. 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 */ 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 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 */ 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 3293 private int sanitizeColor() { 3294 if (mColor != COLOR_DEFAULT) { 3295 mColor |= 0xFF000000; // no alpha for custom colors 3296 } 3297 return mColor; 3298 } 3299 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 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 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 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 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 */ 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 */ 3678 public Notification buildInto(Notification n) { 3679 build().cloneInto(n, true); 3680 return n; 3681 } 3682 3683 private int getBaseLayoutResource() { 3684 return R.layout.notification_template_material_base; 3685 } 3686 3687 private int getBigBaseLayoutResource() { 3688 return R.layout.notification_template_material_big_base; 3689 } 3690 3691 private int getBigPictureLayoutResource() { 3692 return R.layout.notification_template_material_big_picture; 3693 } 3694 3695 private int getBigTextLayoutResource() { 3696 return R.layout.notification_template_material_big_text; 3697 } 3698 3699 private int getInboxLayoutResource() { 3700 return R.layout.notification_template_material_inbox; 3701 } 3702 3703 private int getActionLayoutResource() { 3704 return R.layout.notification_material_action; 3705 } 3706 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 */ 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 */ 3742 protected void internalSetSummaryText(CharSequence cs) { 3743 mSummaryText = cs; 3744 mSummaryTextSet = true; 3745 } 3746 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 3756 protected void checkBuilder() { 3757 if (mBuilder == null) { 3758 throw new IllegalArgumentException("Style requires a valid Builder object"); 3759 } 3760 } 3761 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 */ 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 */ 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 */ 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 */ 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 */ 3853 public void purgeResources() {} 3854 3855 // The following methods are split out so we can re-create notification partially. 3856 /** 3857 * @hide 3858 */ 3859 protected void populateTickerView(Notification wip) {} 3860 /** 3861 * @hide 3862 */ 3863 protected void populateContentView(Notification wip) {} 3864 3865 /** 3866 * @hide 3867 */ 3868 protected void populateBigContentView(Notification wip) {} 3869 3870 /** 3871 * @hide 3872 */ 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 */ 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 */ 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 3918 public BigPictureStyle() { 3919 } 3920 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 */ 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 */ 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 */ 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 */ 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 */ 3960 public BigPictureStyle bigLargeIcon(Icon icon) { 3961 mBigLargeIconSet = true; 3962 mBigLargeIcon = icon; 3963 return this; 3964 } 3965 3966 /** 3967 * @hide 3968 */ 3969 @Override 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 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 */ 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 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 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 4068 public BigTextStyle() { 4069 } 4070 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 */ 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 */ 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 */ 4096 public BigTextStyle bigText(CharSequence cs) { 4097 mBigText = safeCharSequence(cs); 4098 return this; 4099 } 4100 4101 /** 4102 * @hide 4103 */ 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 4114 protected void restoreFromExtras(Bundle extras) { 4115 super.restoreFromExtras(extras); 4116 4117 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 4118 } 4119 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 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 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 4194 public InboxStyle() { 4195 } 4196 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 */ 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 */ 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 */ 4221 public InboxStyle addLine(CharSequence cs) { 4222 mTexts.add(safeCharSequence(cs)); 4223 return this; 4224 } 4225 4226 /** 4227 * @hide 4228 */ 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 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 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 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 4358 public MediaStyle() { 4359 } 4360 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 */ 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 */ 4380 public MediaStyle setMediaSession(MediaSession.Token token) { 4381 mToken = token; 4382 return this; 4383 } 4384 4385 /** 4386 * @hide 4387 */ 4388 @Override 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 4401 public void populateContentView(Notification wip) { 4402 mBuilder.setBuilderContentView(wip, makeMediaContentView()); 4403 } 4404 4405 /** 4406 * @hide 4407 */ 4408 @Override 4409 public void populateBigContentView(Notification wip) { 4410 mBuilder.setBuilderBigContentView(wip, makeMediaBigContentView()); 4411 } 4412 4413 /** @hide */ 4414 @Override 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 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 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 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 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 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 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 */ 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 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 */ 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 */ 4715 public WearableExtender() { 4716 } 4717 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 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 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 */ 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 */ 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 */ 4858 public WearableExtender clearActions() { 4859 mActions.clear(); 4860 return this; 4861 } 4862 4863 /** 4864 * Get the wearable actions present on this notification. 4865 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 4986 public Bitmap getBackground() { 4987 return mBackground; 4988 } 4989 4990 /** 4991 * Set an icon that goes with the content of this notification. 4992 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 5253 public int getHintScreenTimeout() { 5254 return mHintScreenTimeout; 5255 } 5256 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 5476 public PendingIntent getReadPendingIntent() { 5477 return mReadPendingIntent; 5478 } 5479 5480 /** 5481 * Gets the participants in the conversation. 5482 */ 5483 public String[] getParticipants() { 5484 return mParticipants; 5485 } 5486 5487 /** 5488 * Gets the firs participant in the conversation. 5489 */ 5490 public String getParticipant() { 5491 return mParticipants.length > 0 ? mParticipants[0] : null; 5492 } 5493 5494 /** 5495 * Gets the timestamp of the conversation. 5496 */ 5497 public long getLatestTimestamp() { 5498 return mLatestTimestamp; 5499 } 5500 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 { 5681 public BuilderRemoteViews(Parcel parcel) { 5682 super(parcel); 5683 } 5684 5685 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 5686 super(appInfo, layoutId); 5687 } 5688 5689 @Override 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