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