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