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