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