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