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