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