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