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