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