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