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