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