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