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