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