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