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 notificationfor example, to redact some portionssee 674 * {@link Builder#setPublicVersion(Notification)}. 675 * 676 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 677 * and ticker until the user has bypassed the lockscreen. 678 */ 679 public @Visibility int visibility; 680 681 /** @hide */ 682 @IntDef(prefix = { "VISIBILITY_" }, value = { 683 VISIBILITY_PUBLIC, 684 VISIBILITY_PRIVATE, 685 VISIBILITY_SECRET, 686 }) 687 @Retention(RetentionPolicy.SOURCE) 688 public @interface Visibility {} 689 690 /** 691 * Notification visibility: Show this notification in its entirety on all lockscreens. 692 * 693 * {@see #visibility} 694 */ 695 public static final int VISIBILITY_PUBLIC = 1; 696 697 /** 698 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 699 * private information on secure lockscreens. 700 * 701 * {@see #visibility} 702 */ 703 public static final int VISIBILITY_PRIVATE = 0; 704 705 /** 706 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 707 * 708 * {@see #visibility} 709 */ 710 public static final int VISIBILITY_SECRET = -1; 711 712 /** 713 * Notification category: incoming call (voice or video) or similar synchronous communication request. 714 */ 715 public static final String CATEGORY_CALL = "call"; 716 717 /** 718 * Notification category: incoming direct message (SMS, instant message, etc.). 719 */ 720 public static final String CATEGORY_MESSAGE = "msg"; 721 722 /** 723 * Notification category: asynchronous bulk message (email). 724 */ 725 public static final String CATEGORY_EMAIL = "email"; 726 727 /** 728 * Notification category: calendar event. 729 */ 730 public static final String CATEGORY_EVENT = "event"; 731 732 /** 733 * Notification category: promotion or advertisement. 734 */ 735 public static final String CATEGORY_PROMO = "promo"; 736 737 /** 738 * Notification category: alarm or timer. 739 */ 740 public static final String CATEGORY_ALARM = "alarm"; 741 742 /** 743 * Notification category: progress of a long-running background operation. 744 */ 745 public static final String CATEGORY_PROGRESS = "progress"; 746 747 /** 748 * Notification category: social network or sharing update. 749 */ 750 public static final String CATEGORY_SOCIAL = "social"; 751 752 /** 753 * Notification category: error in background operation or authentication status. 754 */ 755 public static final String CATEGORY_ERROR = "err"; 756 757 /** 758 * Notification category: media transport control for playback. 759 */ 760 public static final String CATEGORY_TRANSPORT = "transport"; 761 762 /** 763 * Notification category: system or device status update. Reserved for system use. 764 */ 765 public static final String CATEGORY_SYSTEM = "sys"; 766 767 /** 768 * Notification category: indication of running background service. 769 */ 770 public static final String CATEGORY_SERVICE = "service"; 771 772 /** 773 * Notification category: a specific, timely recommendation for a single thing. 774 * For example, a news app might want to recommend a news story it believes the user will 775 * want to read next. 776 */ 777 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 778 779 /** 780 * Notification category: ongoing information about device or contextual status. 781 */ 782 public static final String CATEGORY_STATUS = "status"; 783 784 /** 785 * Notification category: user-scheduled reminder. 786 */ 787 public static final String CATEGORY_REMINDER = "reminder"; 788 789 /** 790 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 791 * that best describes this Notification. May be used by the system for ranking and filtering. 792 */ 793 public String category; 794 795 private String mGroupKey; 796 797 /** 798 * Get the key used to group this notification into a cluster or stack 799 * with other notifications on devices which support such rendering. 800 */ 801 public String getGroup() { 802 return mGroupKey; 803 } 804 805 private String mSortKey; 806 807 /** 808 * Get a sort key that orders this notification among other notifications from the 809 * same package. This can be useful if an external sort was already applied and an app 810 * would like to preserve this. Notifications will be sorted lexicographically using this 811 * value, although providing different priorities in addition to providing sort key may 812 * cause this value to be ignored. 813 * 814 * <p>This sort key can also be used to order members of a notification group. See 815 * {@link Builder#setGroup}. 816 * 817 * @see String#compareTo(String) 818 */ 819 public String getSortKey() { 820 return mSortKey; 821 } 822 823 /** 824 * Additional semantic data to be carried around with this Notification. 825 * <p> 826 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 827 * APIs, and are intended to be used by 828 * {@link android.service.notification.NotificationListenerService} implementations to extract 829 * detailed information from notification objects. 830 */ 831 public Bundle extras = new Bundle(); 832 833 /** 834 * All pending intents in the notification as the system needs to be able to access them but 835 * touching the extras bundle in the system process is not safe because the bundle may contain 836 * custom parcelable objects. 837 * 838 * @hide 839 */ 840 public ArraySet<PendingIntent> allPendingIntents; 841 842 /** 843 * 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 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 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)}! */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 1494 public int describeContents() { 1495 return 0; 1496 } 1497 @Override 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 1653 public boolean isAvailableOffline() { 1654 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 1655 } 1656 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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") 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 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 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 */ 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 */ 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 */ 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 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 2159 public int describeContents() { 2160 return 0; 2161 } 2162 2163 /** 2164 * Flatten this notification into a parcel. 2165 */ 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 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 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 */ 2418 public static void addFieldsFromContext(Context context, Notification notification) { 2419 addFieldsFromContext(context.getApplicationInfo(), notification); 2420 } 2421 2422 /** 2423 * @hide 2424 */ 2425 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 2426 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 2427 } 2428 2429 @Override 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 */ 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 */ 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 2541 public String getChannel() { 2542 return mChannelId; 2543 } 2544 2545 /** 2546 * Returns the id of the channel this notification posts to. 2547 */ 2548 public String getChannelId() { 2549 return mChannelId; 2550 } 2551 2552 /** @removed */ 2553 @Deprecated 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 */ 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 */ 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 */ 2581 public String getShortcutId() { 2582 return mShortcutId; 2583 } 2584 2585 2586 /** 2587 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 2588 */ 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 */ 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 */ 2610 public Icon getSmallIcon() { 2611 return mSmallIcon; 2612 } 2613 2614 /** 2615 * Used when notifying to clean up legacy small icons. 2616 * @hide 2617 */ 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 */ 2627 public Icon getLargeIcon() { 2628 return mLargeIcon; 2629 } 2630 2631 /** 2632 * @hide 2633 */ 2634 public boolean isGroupSummary() { 2635 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 2636 } 2637 2638 /** 2639 * @hide 2640 */ 2641 public boolean isGroupChild() { 2642 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 2643 } 2644 2645 /** 2646 * @hide 2647 */ 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 */ 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 2772 public Builder(Context context) { 2773 this(context, (Notification) null); 2774 } 2775 2776 /** 2777 * @hide 2778 */ 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 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 */ 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 */ 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 */ 2877 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 2878 mN.mGroupAlertBehavior = groupAlertBehavior; 2879 return this; 2880 } 2881 2882 /** @removed */ 2883 @Deprecated 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 */ 2892 public Builder setChannelId(String channelId) { 2893 mN.mChannelId = channelId; 2894 return this; 2895 } 2896 2897 /** @removed */ 2898 @Deprecated 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 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 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 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 3603 public Bundle getExtras() { 3604 return mUserExtras; 3605 } 3606 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 3725 public Builder extend(Extender extender) { 3726 extender.extend(this); 3727 return this; 3728 } 3729 3730 /** 3731 * @hide 3732 */ 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 */ 3749 public Builder setColor(@ColorInt int argb) { 3750 mN.color = argb; 3751 sanitizeColor(); 3752 return this; 3753 } 3754 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 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 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 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 */ 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 3824 private void resetContentMargins(RemoteViews contentView) { 3825 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); 3826 contentView.setViewLayoutMarginEndDimen(R.id.text, 0); 3827 } 3828 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 */ 3836 private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) { 3837 return applyStandardTemplate(resId, mParams.reset().hasProgress(hasProgress) 3838 .fillTextsFrom(this)); 3839 } 3840 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 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 3886 public int getPrimaryTextColor() { 3887 ensureColors(); 3888 return mPrimaryTextColor; 3889 } 3890 3891 /** 3892 * @return the secondary text color 3893 * @hide 3894 */ 3895 @VisibleForTesting 3896 public int getSecondaryTextColor() { 3897 ensureColors(); 3898 return mSecondaryTextColor; 3899 } 3900 3901 private int getActionBarColor() { 3902 ensureColors(); 3903 return mActionBarColor; 3904 } 3905 3906 private int getActionBarColorDeEmphasized() { 3907 int backgroundColor = getBackgroundColor(); 3908 return NotificationColorUtil.getShiftedColor(backgroundColor, 12); 3909 } 3910 3911 private void setTextViewColorSecondary(RemoteViews contentView, int id) { 3912 ensureColors(); 3913 contentView.setTextColor(id, mSecondaryTextColor); 3914 } 3915 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 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 */ 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 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 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 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 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 */ 4087 private int getPrimaryHighlightColor() { 4088 return isColorized() ? getPrimaryTextColor() : resolveContrastColor(); 4089 } 4090 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 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 */ 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 } 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 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 */ 4193 private boolean showsTimeOrChronometer() { 4194 return mN.showsTime() || mN.showsChronometer(); 4195 } 4196 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 4214 private RemoteViews applyStandardTemplateWithActions(int layoutId) { 4215 return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this)); 4216 } 4217 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 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 4451 public RemoteViews makePublicAmbientNotification() { 4452 return makePublicView(true /* ambient */); 4453 } 4454 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 */ 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 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 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 */ 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 */ 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 */ 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 */ 5799 public List<Message> getMessages() { 5800 return mMessages; 5801 } 5802 5803 /** 5804 * Gets the list of historic {@code Message}s in the notification. 5805 */ 5806 public List<Message> getHistoricMessages() { 5807 return mHistoricMessages; 5808 } 5809 5810 /** 5811 * @hide 5812 */ 5813 @Override 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 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 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 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 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 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 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 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 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 */ 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 */ 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 */ 6131 public CharSequence getText() { 6132 return mText; 6133 } 6134 6135 /** 6136 * Get the time at which this message arrived 6137 */ 6138 public long getTimestamp() { 6139 return mTimestamp; 6140 } 6141 6142 /** 6143 * Get the extras Bundle for this message. 6144 */ 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 */ 6152 public CharSequence getSender() { 6153 return mSender; 6154 } 6155 6156 /** 6157 * Get the MIME type of the data pointed to by the Uri 6158 */ 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 */ 6167 public Uri getDataUri() { 6168 return mDataUri; 6169 } 6170 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 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 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 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 6261 public InboxStyle() { 6262 } 6263 6264 /** 6265 * @deprecated use {@code InboxStyle()}. 6266 */ 6267 @Deprecated 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 */ 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 */ 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 */ 6292 public InboxStyle addLine(CharSequence cs) { 6293 mTexts.add(safeCharSequence(cs)); 6294 return this; 6295 } 6296 6297 /** 6298 * @hide 6299 */ 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 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 */ 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 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 6444 public MediaStyle() { 6445 } 6446 6447 /** 6448 * @deprecated use {@code MediaStyle()}. 6449 */ 6450 @Deprecated 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 */ 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 */ 6470 public MediaStyle setMediaSession(MediaSession.Token token) { 6471 mToken = token; 6472 return this; 6473 } 6474 6475 /** 6476 * @hide 6477 */ 6478 @Override 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 6491 public RemoteViews makeContentView(boolean increasedHeight) { 6492 return makeMediaContentView(); 6493 } 6494 6495 /** 6496 * @hide 6497 */ 6498 @Override 6499 public RemoteViews makeBigContentView() { 6500 return makeMediaBigContentView(); 6501 } 6502 6503 /** 6504 * @hide 6505 */ 6506 @Override 6507 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 6508 RemoteViews expanded = makeMediaBigContentView(); 6509 return expanded != null ? expanded : makeMediaContentView(); 6510 } 6511 6512 /** @hide */ 6513 @Override 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 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 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 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 6587 private int getPrimaryHighlightColor() { 6588 return mBuilder.getPrimaryHighlightColor(); 6589 } 6590 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 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 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 6657 public DecoratedCustomViewStyle() { 6658 } 6659 6660 /** 6661 * @hide 6662 */ 6663 public boolean displayCustomViewInline() { 6664 return true; 6665 } 6666 6667 /** 6668 * @hide 6669 */ 6670 @Override 6671 public RemoteViews makeContentView(boolean increasedHeight) { 6672 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 6673 } 6674 6675 /** 6676 * @hide 6677 */ 6678 @Override 6679 public RemoteViews makeBigContentView() { 6680 return makeDecoratedBigContentView(); 6681 } 6682 6683 /** 6684 * @hide 6685 */ 6686 @Override 6687 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 6688 return makeDecoratedHeadsUpContentView(); 6689 } 6690 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 6704 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 6705 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 6706 mBuilder.getBaseLayoutResource()); 6707 buildIntoRemoteViewContent(remoteViews, customContent); 6708 return remoteViews; 6709 } 6710 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 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 6774 public DecoratedMediaCustomViewStyle() { 6775 } 6776 6777 /** 6778 * @hide 6779 */ 6780 public boolean displayCustomViewInline() { 6781 return true; 6782 } 6783 6784 /** 6785 * @hide 6786 */ 6787 @Override 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 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 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 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 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 */ 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 */ 7020 public WearableExtender() { 7021 } 7022 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 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 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 */ 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 */ 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 */ 7173 public WearableExtender clearActions() { 7174 mActions.clear(); 7175 return this; 7176 } 7177 7178 /** 7179 * Get the wearable actions present on this notification. 7180 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 7301 public Bitmap getBackground() { 7302 return mBackground; 7303 } 7304 7305 /** 7306 * Set an icon that goes with the content of this notification. 7307 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 7657 public String getBridgeTag() { 7658 return mBridgeTag; 7659 } 7660 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 */ 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 */ 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 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 7880 public PendingIntent getReadPendingIntent() { 7881 return mReadPendingIntent; 7882 } 7883 7884 /** 7885 * Gets the participants in the conversation. 7886 */ 7887 public String[] getParticipants() { 7888 return mParticipants; 7889 } 7890 7891 /** 7892 * Gets the firs participant in the conversation. 7893 */ 7894 public String getParticipant() { 7895 return mParticipants.length > 0 ? mParticipants[0] : null; 7896 } 7897 7898 /** 7899 * Gets the timestamp of the conversation. 7900 */ 7901 public long getLatestTimestamp() { 7902 return mLatestTimestamp; 7903 } 7904 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 8182 public TvExtender setChannelId(String channelId) { 8183 mChannelId = channelId; 8184 return this; 8185 } 8186 8187 /** @removed */ 8188 @Deprecated 8189 public String getChannel() { 8190 return mChannelId; 8191 } 8192 8193 /** 8194 * Returns the id of the channel this notification posts to on TV. 8195 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 { 8258 public BuilderRemoteViews(Parcel parcel) { 8259 super(parcel); 8260 } 8261 8262 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 8263 super(appInfo, layoutId); 8264 } 8265 8266 @Override 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 8283 final StandardTemplateParams reset() { 8284 hasProgress = true; 8285 ambient = false; 8286 title = null; 8287 text = null; 8288 return this; 8289 } 8290 8291 final StandardTemplateParams hasProgress(boolean hasProgress) { 8292 this.hasProgress = hasProgress; 8293 return this; 8294 } 8295 8296 final StandardTemplateParams title(CharSequence title) { 8297 this.title = title; 8298 return this; 8299 } 8300 8301 final StandardTemplateParams text(CharSequence text) { 8302 this.text = text; 8303 return this; 8304 } 8305 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 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