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