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