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