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.compat.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 /** @hide */ 632 @IntDef({FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, 633 FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, 634 FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE}) 635 @Retention(RetentionPolicy.SOURCE) 636 public @interface NotificationFlags{}; 637 638 public int flags; 639 640 /** @hide */ 641 @IntDef(prefix = { "PRIORITY_" }, value = { 642 PRIORITY_DEFAULT, 643 PRIORITY_LOW, 644 PRIORITY_MIN, 645 PRIORITY_HIGH, 646 PRIORITY_MAX 647 }) 648 @Retention(RetentionPolicy.SOURCE) 649 public @interface Priority {} 650 651 /** 652 * Default notification {@link #priority}. If your application does not prioritize its own 653 * notifications, use this value for all notifications. 654 * 655 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. 656 */ 657 @Deprecated 658 public static final int PRIORITY_DEFAULT = 0; 659 660 /** 661 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 662 * items smaller, or at a different position in the list, compared with your app's 663 * {@link #PRIORITY_DEFAULT} items. 664 * 665 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. 666 */ 667 @Deprecated 668 public static final int PRIORITY_LOW = -1; 669 670 /** 671 * Lowest {@link #priority}; these items might not be shown to the user except under special 672 * circumstances, such as detailed notification logs. 673 * 674 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. 675 */ 676 @Deprecated 677 public static final int PRIORITY_MIN = -2; 678 679 /** 680 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 681 * show these items larger, or at a different position in notification lists, compared with 682 * your app's {@link #PRIORITY_DEFAULT} items. 683 * 684 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 685 */ 686 @Deprecated 687 public static final int PRIORITY_HIGH = 1; 688 689 /** 690 * Highest {@link #priority}, for your application's most important items that require the 691 * user's prompt attention or input. 692 * 693 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 694 */ 695 @Deprecated 696 public static final int PRIORITY_MAX = 2; 697 698 /** 699 * Relative priority for this notification. 700 * 701 * Priority is an indication of how much of the user's valuable attention should be consumed by 702 * this notification. Low-priority notifications may be hidden from the user in certain 703 * situations, while the user might be interrupted for a higher-priority notification. The 704 * system will make a determination about how to interpret this priority when presenting 705 * the notification. 706 * 707 * <p> 708 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 709 * as a heads-up notification. 710 * </p> 711 * 712 * @deprecated use {@link NotificationChannel#getImportance()} instead. 713 */ 714 @Priority 715 @Deprecated 716 public int priority; 717 718 /** 719 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 720 * to be applied by the standard Style templates when presenting this notification. 721 * 722 * The current template design constructs a colorful header image by overlaying the 723 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 724 * ignored. 725 */ 726 @ColorInt 727 public int color = COLOR_DEFAULT; 728 729 /** 730 * Special value of {@link #color} telling the system not to decorate this notification with 731 * any special color but instead use default colors when presenting this notification. 732 */ 733 @ColorInt 734 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 735 736 /** 737 * Special value of {@link #color} used as a place holder for an invalid color. 738 * @hide 739 */ 740 @ColorInt 741 public static final int COLOR_INVALID = 1; 742 743 /** 744 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 745 * the notification's presence and contents in untrusted situations (namely, on the secure 746 * lockscreen). 747 * 748 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 749 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 750 * shown in all situations, but the contents are only available if the device is unlocked for 751 * the appropriate user. 752 * 753 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 754 * can be read even in an "insecure" context (that is, above a secure lockscreen). 755 * To modify the public version of this notification—for example, to redact some portions—see 756 * {@link Builder#setPublicVersion(Notification)}. 757 * 758 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 759 * and ticker until the user has bypassed the lockscreen. 760 */ 761 public @Visibility int visibility; 762 763 /** @hide */ 764 @IntDef(prefix = { "VISIBILITY_" }, value = { 765 VISIBILITY_PUBLIC, 766 VISIBILITY_PRIVATE, 767 VISIBILITY_SECRET, 768 }) 769 @Retention(RetentionPolicy.SOURCE) 770 public @interface Visibility {} 771 772 /** 773 * Notification visibility: Show this notification in its entirety on all lockscreens. 774 * 775 * {@see #visibility} 776 */ 777 public static final int VISIBILITY_PUBLIC = 1; 778 779 /** 780 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 781 * private information on secure lockscreens. 782 * 783 * {@see #visibility} 784 */ 785 public static final int VISIBILITY_PRIVATE = 0; 786 787 /** 788 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 789 * 790 * {@see #visibility} 791 */ 792 public static final int VISIBILITY_SECRET = -1; 793 794 /** 795 * Notification category: incoming call (voice or video) or similar synchronous communication request. 796 */ 797 public static final String CATEGORY_CALL = "call"; 798 799 /** 800 * Notification category: map turn-by-turn navigation. 801 */ 802 public static final String CATEGORY_NAVIGATION = "navigation"; 803 804 /** 805 * Notification category: incoming direct message (SMS, instant message, etc.). 806 */ 807 public static final String CATEGORY_MESSAGE = "msg"; 808 809 /** 810 * Notification category: asynchronous bulk message (email). 811 */ 812 public static final String CATEGORY_EMAIL = "email"; 813 814 /** 815 * Notification category: calendar event. 816 */ 817 public static final String CATEGORY_EVENT = "event"; 818 819 /** 820 * Notification category: promotion or advertisement. 821 */ 822 public static final String CATEGORY_PROMO = "promo"; 823 824 /** 825 * Notification category: alarm or timer. 826 */ 827 public static final String CATEGORY_ALARM = "alarm"; 828 829 /** 830 * Notification category: progress of a long-running background operation. 831 */ 832 public static final String CATEGORY_PROGRESS = "progress"; 833 834 /** 835 * Notification category: social network or sharing update. 836 */ 837 public static final String CATEGORY_SOCIAL = "social"; 838 839 /** 840 * Notification category: error in background operation or authentication status. 841 */ 842 public static final String CATEGORY_ERROR = "err"; 843 844 /** 845 * Notification category: media transport control for playback. 846 */ 847 public static final String CATEGORY_TRANSPORT = "transport"; 848 849 /** 850 * Notification category: system or device status update. Reserved for system use. 851 */ 852 public static final String CATEGORY_SYSTEM = "sys"; 853 854 /** 855 * Notification category: indication of running background service. 856 */ 857 public static final String CATEGORY_SERVICE = "service"; 858 859 /** 860 * Notification category: a specific, timely recommendation for a single thing. 861 * For example, a news app might want to recommend a news story it believes the user will 862 * want to read next. 863 */ 864 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 865 866 /** 867 * Notification category: ongoing information about device or contextual status. 868 */ 869 public static final String CATEGORY_STATUS = "status"; 870 871 /** 872 * Notification category: user-scheduled reminder. 873 */ 874 public static final String CATEGORY_REMINDER = "reminder"; 875 876 /** 877 * Notification category: extreme car emergencies. 878 * @hide 879 */ 880 @SystemApi 881 public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; 882 883 /** 884 * Notification category: car warnings. 885 * @hide 886 */ 887 @SystemApi 888 public static final String CATEGORY_CAR_WARNING = "car_warning"; 889 890 /** 891 * Notification category: general car system information. 892 * @hide 893 */ 894 @SystemApi 895 public static final String CATEGORY_CAR_INFORMATION = "car_information"; 896 897 /** 898 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 899 * that best describes this Notification. May be used by the system for ranking and filtering. 900 */ 901 public String category; 902 903 @UnsupportedAppUsage 904 private String mGroupKey; 905 906 /** 907 * Get the key used to group this notification into a cluster or stack 908 * with other notifications on devices which support such rendering. 909 */ getGroup()910 public String getGroup() { 911 return mGroupKey; 912 } 913 914 private String mSortKey; 915 916 /** 917 * Get a sort key that orders this notification among other notifications from the 918 * same package. This can be useful if an external sort was already applied and an app 919 * would like to preserve this. Notifications will be sorted lexicographically using this 920 * value, although providing different priorities in addition to providing sort key may 921 * cause this value to be ignored. 922 * 923 * <p>This sort key can also be used to order members of a notification group. See 924 * {@link Builder#setGroup}. 925 * 926 * @see String#compareTo(String) 927 */ getSortKey()928 public String getSortKey() { 929 return mSortKey; 930 } 931 932 /** 933 * Additional semantic data to be carried around with this Notification. 934 * <p> 935 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 936 * APIs, and are intended to be used by 937 * {@link android.service.notification.NotificationListenerService} implementations to extract 938 * detailed information from notification objects. 939 */ 940 public Bundle extras = new Bundle(); 941 942 /** 943 * All pending intents in the notification as the system needs to be able to access them but 944 * touching the extras bundle in the system process is not safe because the bundle may contain 945 * custom parcelable objects. 946 * 947 * @hide 948 */ 949 @UnsupportedAppUsage 950 public ArraySet<PendingIntent> allPendingIntents; 951 952 /** 953 * Token identifying the notification that is applying doze/bgcheck whitelisting to the 954 * pending intents inside of it, so only those will get the behavior. 955 * 956 * @hide 957 */ 958 private IBinder mWhitelistToken; 959 960 /** 961 * Must be set by a process to start associating tokens with Notification objects 962 * coming in to it. This is set by NotificationManagerService. 963 * 964 * @hide 965 */ 966 static public IBinder processWhitelistToken; 967 968 /** 969 * {@link #extras} key: this is the title of the notification, 970 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 971 */ 972 public static final String EXTRA_TITLE = "android.title"; 973 974 /** 975 * {@link #extras} key: this is the title of the notification when shown in expanded form, 976 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 977 */ 978 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 979 980 /** 981 * {@link #extras} key: this is the main text payload, as supplied to 982 * {@link Builder#setContentText(CharSequence)}. 983 */ 984 public static final String EXTRA_TEXT = "android.text"; 985 986 /** 987 * {@link #extras} key: this is a third line of text, as supplied to 988 * {@link Builder#setSubText(CharSequence)}. 989 */ 990 public static final String EXTRA_SUB_TEXT = "android.subText"; 991 992 /** 993 * {@link #extras} key: this is the remote input history, as supplied to 994 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 995 * 996 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 997 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 998 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 999 * notifications once the other party has responded). 1000 * 1001 * The extra with this key is of type CharSequence[] and contains the most recent entry at 1002 * the 0 index, the second most recent at the 1 index, etc. 1003 * 1004 * @see Builder#setRemoteInputHistory(CharSequence[]) 1005 */ 1006 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 1007 1008 /** 1009 * {@link #extras} key: boolean as supplied to 1010 * {@link Builder#setShowRemoteInputSpinner(boolean)}. 1011 * 1012 * If set to true, then the view displaying the remote input history from 1013 * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner. 1014 * 1015 * @see Builder#setShowRemoteInputSpinner(boolean) 1016 * @hide 1017 */ 1018 public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner"; 1019 1020 /** 1021 * {@link #extras} key: boolean as supplied to 1022 * {@link Builder#setHideSmartReplies(boolean)}. 1023 * 1024 * If set to true, then any smart reply buttons will be hidden. 1025 * 1026 * @see Builder#setHideSmartReplies(boolean) 1027 * @hide 1028 */ 1029 public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies"; 1030 1031 /** 1032 * {@link #extras} key: this is a small piece of additional text as supplied to 1033 * {@link Builder#setContentInfo(CharSequence)}. 1034 */ 1035 public static final String EXTRA_INFO_TEXT = "android.infoText"; 1036 1037 /** 1038 * {@link #extras} key: this is a line of summary information intended to be shown 1039 * alongside expanded notifications, as supplied to (e.g.) 1040 * {@link BigTextStyle#setSummaryText(CharSequence)}. 1041 */ 1042 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 1043 1044 /** 1045 * {@link #extras} key: this is the longer text shown in the big form of a 1046 * {@link BigTextStyle} notification, as supplied to 1047 * {@link BigTextStyle#bigText(CharSequence)}. 1048 */ 1049 public static final String EXTRA_BIG_TEXT = "android.bigText"; 1050 1051 /** 1052 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 1053 * supplied to {@link Builder#setSmallIcon(int)}. 1054 * 1055 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources. 1056 */ 1057 @Deprecated 1058 public static final String EXTRA_SMALL_ICON = "android.icon"; 1059 1060 /** 1061 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 1062 * notification payload, as 1063 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 1064 * 1065 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources. 1066 */ 1067 @Deprecated 1068 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 1069 1070 /** 1071 * {@link #extras} key: this is a bitmap to be used instead of the one from 1072 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 1073 * shown in its expanded form, as supplied to 1074 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 1075 */ 1076 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 1077 1078 /** 1079 * {@link #extras} key: this is the progress value supplied to 1080 * {@link Builder#setProgress(int, int, boolean)}. 1081 */ 1082 public static final String EXTRA_PROGRESS = "android.progress"; 1083 1084 /** 1085 * {@link #extras} key: this is the maximum value supplied to 1086 * {@link Builder#setProgress(int, int, boolean)}. 1087 */ 1088 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 1089 1090 /** 1091 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 1092 * {@link Builder#setProgress(int, int, boolean)}. 1093 */ 1094 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 1095 1096 /** 1097 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 1098 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 1099 * {@link Builder#setUsesChronometer(boolean)}. 1100 */ 1101 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 1102 1103 /** 1104 * {@link #extras} key: whether the chronometer set on the notification should count down 1105 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 1106 * This extra is a boolean. The default is false. 1107 */ 1108 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 1109 1110 /** 1111 * {@link #extras} key: whether {@link #when} should be shown, 1112 * as supplied to {@link Builder#setShowWhen(boolean)}. 1113 */ 1114 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 1115 1116 /** 1117 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 1118 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 1119 */ 1120 public static final String EXTRA_PICTURE = "android.picture"; 1121 1122 /** 1123 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 1124 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 1125 */ 1126 public static final String EXTRA_TEXT_LINES = "android.textLines"; 1127 1128 /** 1129 * {@link #extras} key: A string representing the name of the specific 1130 * {@link android.app.Notification.Style} used to create this notification. 1131 */ 1132 public static final String EXTRA_TEMPLATE = "android.template"; 1133 1134 /** 1135 * {@link #extras} key: A String array containing the people that this notification relates to, 1136 * each of which was supplied to {@link Builder#addPerson(String)}. 1137 * 1138 * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} 1139 */ 1140 public static final String EXTRA_PEOPLE = "android.people"; 1141 1142 /** 1143 * {@link #extras} key: An arrayList of {@link Person} objects containing the people that 1144 * this notification relates to. 1145 */ 1146 public static final String EXTRA_PEOPLE_LIST = "android.people.list"; 1147 1148 /** 1149 * Allow certain system-generated notifications to appear before the device is provisioned. 1150 * Only available to notifications coming from the android package. 1151 * @hide 1152 */ 1153 @SystemApi 1154 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP) 1155 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 1156 1157 /** 1158 * {@link #extras} key: 1159 * flat {@link String} representation of a {@link android.content.ContentUris content URI} 1160 * pointing to an image that can be displayed in the background when the notification is 1161 * selected. Used on television platforms. The URI must point to an image stream suitable for 1162 * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1163 * BitmapFactory.decodeStream}; all other content types will be ignored. 1164 */ 1165 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 1166 1167 /** 1168 * {@link #extras} key: A 1169 * {@link android.media.session.MediaSession.Token} associated with a 1170 * {@link android.app.Notification.MediaStyle} notification. 1171 */ 1172 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 1173 1174 /** 1175 * {@link #extras} key: the indices of actions to be shown in the compact view, 1176 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 1177 */ 1178 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 1179 1180 /** 1181 * {@link #extras} key: the username to be displayed for all messages sent by the user including 1182 * direct replies 1183 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1184 * {@link CharSequence} 1185 * 1186 * @deprecated use {@link #EXTRA_MESSAGING_PERSON} 1187 */ 1188 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 1189 1190 /** 1191 * {@link #extras} key: the person to be displayed for all messages sent by the user including 1192 * direct replies 1193 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1194 * {@link Person} 1195 */ 1196 public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; 1197 1198 /** 1199 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 1200 * represented by a {@link android.app.Notification.MessagingStyle} 1201 */ 1202 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 1203 1204 /** 1205 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 1206 * bundles provided by a 1207 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1208 * array of bundles. 1209 */ 1210 public static final String EXTRA_MESSAGES = "android.messages"; 1211 1212 /** 1213 * {@link #extras} key: an array of 1214 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic} 1215 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a 1216 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1217 * array of bundles. 1218 */ 1219 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; 1220 1221 /** 1222 * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification 1223 * represents a group conversation. 1224 */ 1225 public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; 1226 1227 /** 1228 * {@link #extras} key: whether the notification should be colorized as 1229 * supplied to {@link Builder#setColorized(boolean)}. 1230 */ 1231 public static final String EXTRA_COLORIZED = "android.colorized"; 1232 1233 /** 1234 * @hide 1235 */ 1236 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 1237 1238 /** 1239 * @hide 1240 */ 1241 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 1242 1243 /** 1244 * @hide 1245 */ 1246 public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images"; 1247 1248 /** 1249 * {@link #extras} key: the audio contents of this notification. 1250 * 1251 * This is for use when rendering the notification on an audio-focused interface; 1252 * the audio contents are a complete sound sample that contains the contents/body of the 1253 * notification. This may be used in substitute of a Text-to-Speech reading of the 1254 * notification. For example if the notification represents a voice message this should point 1255 * to the audio of that message. 1256 * 1257 * The data stored under this key should be a String representation of a Uri that contains the 1258 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 1259 * 1260 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 1261 * has a field for holding data URI. That field can be used for audio. 1262 * See {@code Message#setData}. 1263 * 1264 * Example usage: 1265 * <pre> 1266 * {@code 1267 * Notification.Builder myBuilder = (build your Notification as normal); 1268 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 1269 * } 1270 * </pre> 1271 */ 1272 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 1273 1274 /** @hide */ 1275 @SystemApi 1276 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME) 1277 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 1278 1279 /** 1280 * This is set on the notifications shown by system_server about apps running foreground 1281 * services. It indicates that the notification should be shown 1282 * only if any of the given apps do not already have a properly tagged 1283 * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. 1284 * This is a string array of all package names of the apps. 1285 * @hide 1286 */ 1287 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; 1288 1289 @UnsupportedAppUsage 1290 private Icon mSmallIcon; 1291 @UnsupportedAppUsage 1292 private Icon mLargeIcon; 1293 1294 @UnsupportedAppUsage 1295 private String mChannelId; 1296 private long mTimeout; 1297 1298 private String mShortcutId; 1299 private LocusId mLocusId; 1300 private CharSequence mSettingsText; 1301 1302 private BubbleMetadata mBubbleMetadata; 1303 1304 /** @hide */ 1305 @IntDef(prefix = { "GROUP_ALERT_" }, value = { 1306 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY 1307 }) 1308 @Retention(RetentionPolicy.SOURCE) 1309 public @interface GroupAlertBehavior {} 1310 1311 /** 1312 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 1313 * group with sound or vibration ought to make sound or vibrate (respectively), so this 1314 * notification will not be muted when it is in a group. 1315 */ 1316 public static final int GROUP_ALERT_ALL = 0; 1317 1318 /** 1319 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 1320 * notification in a group should be silenced (no sound or vibration) even if they are posted 1321 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to 1322 * mute this notification if this notification is a group child. This must be applied to all 1323 * children notifications you want to mute. 1324 * 1325 * <p> For example, you might want to use this constant if you post a number of children 1326 * notifications at once (say, after a periodic sync), and only need to notify the user 1327 * audibly once. 1328 */ 1329 public static final int GROUP_ALERT_SUMMARY = 1; 1330 1331 /** 1332 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 1333 * notification in a group should be silenced (no sound or vibration) even if they are 1334 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant 1335 * to mute this notification if this notification is a group summary. 1336 * 1337 * <p>For example, you might want to use this constant if only the children notifications 1338 * in your group have content and the summary is only used to visually group notifications 1339 * rather than to alert the user that new information is available. 1340 */ 1341 public static final int GROUP_ALERT_CHILDREN = 2; 1342 1343 private int mGroupAlertBehavior = GROUP_ALERT_ALL; 1344 1345 /** 1346 * If this notification is being shown as a badge, always show as a number. 1347 */ 1348 public static final int BADGE_ICON_NONE = 0; 1349 1350 /** 1351 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to 1352 * represent this notification. 1353 */ 1354 public static final int BADGE_ICON_SMALL = 1; 1355 1356 /** 1357 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to 1358 * represent this notification. 1359 */ 1360 public static final int BADGE_ICON_LARGE = 2; 1361 private int mBadgeIcon = BADGE_ICON_NONE; 1362 1363 /** 1364 * Determines whether the platform can generate contextual actions for a notification. 1365 */ 1366 private boolean mAllowSystemGeneratedContextualActions = true; 1367 1368 /** 1369 * Structure to encapsulate a named action that can be shown as part of this notification. 1370 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1371 * selected by the user. 1372 * <p> 1373 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1374 * or {@link Notification.Builder#addAction(Notification.Action)} 1375 * to attach actions. 1376 */ 1377 public static class Action implements Parcelable { 1378 /** 1379 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of 1380 * {@link RemoteInput}s. 1381 * 1382 * This is intended for {@link RemoteInput}s that only accept data, meaning 1383 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 1384 * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not 1385 * empty. These {@link RemoteInput}s will be ignored by devices that do not 1386 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 1387 * 1388 * You can test if a RemoteInput matches these constraints using 1389 * {@link RemoteInput#isDataOnly}. 1390 */ 1391 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; 1392 1393 /** 1394 * {@link }: No semantic action defined. 1395 */ 1396 public static final int SEMANTIC_ACTION_NONE = 0; 1397 1398 /** 1399 * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies 1400 * may be appropriate. 1401 */ 1402 public static final int SEMANTIC_ACTION_REPLY = 1; 1403 1404 /** 1405 * {@code SemanticAction}: Mark content as read. 1406 */ 1407 public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; 1408 1409 /** 1410 * {@code SemanticAction}: Mark content as unread. 1411 */ 1412 public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; 1413 1414 /** 1415 * {@code SemanticAction}: Delete the content associated with the notification. This 1416 * could mean deleting an email, message, etc. 1417 */ 1418 public static final int SEMANTIC_ACTION_DELETE = 4; 1419 1420 /** 1421 * {@code SemanticAction}: Archive the content associated with the notification. This 1422 * could mean archiving an email, message, etc. 1423 */ 1424 public static final int SEMANTIC_ACTION_ARCHIVE = 5; 1425 1426 /** 1427 * {@code SemanticAction}: Mute the content associated with the notification. This could 1428 * mean silencing a conversation or currently playing media. 1429 */ 1430 public static final int SEMANTIC_ACTION_MUTE = 6; 1431 1432 /** 1433 * {@code SemanticAction}: Unmute the content associated with the notification. This could 1434 * mean un-silencing a conversation or currently playing media. 1435 */ 1436 public static final int SEMANTIC_ACTION_UNMUTE = 7; 1437 1438 /** 1439 * {@code SemanticAction}: Mark content with a thumbs up. 1440 */ 1441 public static final int SEMANTIC_ACTION_THUMBS_UP = 8; 1442 1443 /** 1444 * {@code SemanticAction}: Mark content with a thumbs down. 1445 */ 1446 public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; 1447 1448 /** 1449 * {@code SemanticAction}: Call a contact, group, etc. 1450 */ 1451 public static final int SEMANTIC_ACTION_CALL = 10; 1452 1453 private final Bundle mExtras; 1454 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1455 private Icon mIcon; 1456 private final RemoteInput[] mRemoteInputs; 1457 private boolean mAllowGeneratedReplies = true; 1458 private final @SemanticAction int mSemanticAction; 1459 private final boolean mIsContextual; 1460 1461 /** 1462 * Small icon representing the action. 1463 * 1464 * @deprecated Use {@link Action#getIcon()} instead. 1465 */ 1466 @Deprecated 1467 public int icon; 1468 1469 /** 1470 * Title of the action. 1471 */ 1472 public CharSequence title; 1473 1474 /** 1475 * Intent to send when the user invokes this action. May be null, in which case the action 1476 * may be rendered in a disabled presentation by the system UI. 1477 */ 1478 public PendingIntent actionIntent; 1479 Action(Parcel in)1480 private Action(Parcel in) { 1481 if (in.readInt() != 0) { 1482 mIcon = Icon.CREATOR.createFromParcel(in); 1483 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1484 icon = mIcon.getResId(); 1485 } 1486 } 1487 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1488 if (in.readInt() == 1) { 1489 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1490 } 1491 mExtras = Bundle.setDefusable(in.readBundle(), true); 1492 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1493 mAllowGeneratedReplies = in.readInt() == 1; 1494 mSemanticAction = in.readInt(); 1495 mIsContextual = in.readInt() == 1; 1496 } 1497 1498 /** 1499 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1500 */ 1501 @Deprecated Action(int icon, CharSequence title, PendingIntent intent)1502 public Action(int icon, CharSequence title, PendingIntent intent) { 1503 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, 1504 SEMANTIC_ACTION_NONE, false /* isContextual */); 1505 } 1506 1507 /** 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)1508 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1509 RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1510 @SemanticAction int semanticAction, boolean isContextual) { 1511 this.mIcon = icon; 1512 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1513 this.icon = icon.getResId(); 1514 } 1515 this.title = title; 1516 this.actionIntent = intent; 1517 this.mExtras = extras != null ? extras : new Bundle(); 1518 this.mRemoteInputs = remoteInputs; 1519 this.mAllowGeneratedReplies = allowGeneratedReplies; 1520 this.mSemanticAction = semanticAction; 1521 this.mIsContextual = isContextual; 1522 } 1523 1524 /** 1525 * Return an icon representing the action. 1526 */ getIcon()1527 public Icon getIcon() { 1528 if (mIcon == null && icon != 0) { 1529 // you snuck an icon in here without using the builder; let's try to keep it 1530 mIcon = Icon.createWithResource("", icon); 1531 } 1532 return mIcon; 1533 } 1534 1535 /** 1536 * Get additional metadata carried around with this Action. 1537 */ getExtras()1538 public Bundle getExtras() { 1539 return mExtras; 1540 } 1541 1542 /** 1543 * Return whether the platform should automatically generate possible replies for this 1544 * {@link Action} 1545 */ getAllowGeneratedReplies()1546 public boolean getAllowGeneratedReplies() { 1547 return mAllowGeneratedReplies; 1548 } 1549 1550 /** 1551 * Get the list of inputs to be collected from the user when this action is sent. 1552 * May return null if no remote inputs were added. Only returns inputs which accept 1553 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 1554 */ getRemoteInputs()1555 public RemoteInput[] getRemoteInputs() { 1556 return mRemoteInputs; 1557 } 1558 1559 /** 1560 * Returns the {@code SemanticAction} associated with this {@link Action}. A 1561 * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do 1562 * (eg. reply, mark as read, delete, etc). 1563 */ getSemanticAction()1564 public @SemanticAction int getSemanticAction() { 1565 return mSemanticAction; 1566 } 1567 1568 /** 1569 * Returns whether this is a contextual Action, i.e. whether the action is dependent on the 1570 * notification message body. An example of a contextual action could be an action opening a 1571 * map application with an address shown in the notification. 1572 */ isContextual()1573 public boolean isContextual() { 1574 return mIsContextual; 1575 } 1576 1577 /** 1578 * Get the list of inputs to be collected from the user that ONLY accept data when this 1579 * action is sent. These remote inputs are guaranteed to return true on a call to 1580 * {@link RemoteInput#isDataOnly}. 1581 * 1582 * Returns null if there are no data-only remote inputs. 1583 * 1584 * This method exists so that legacy RemoteInput collectors that pre-date the addition 1585 * of non-textual RemoteInputs do not access these remote inputs. 1586 */ getDataOnlyRemoteInputs()1587 public RemoteInput[] getDataOnlyRemoteInputs() { 1588 return (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS); 1589 } 1590 1591 /** 1592 * Builder class for {@link Action} objects. 1593 */ 1594 public static final class Builder { 1595 @Nullable private final Icon mIcon; 1596 @Nullable private final CharSequence mTitle; 1597 @Nullable private final PendingIntent mIntent; 1598 private boolean mAllowGeneratedReplies = true; 1599 @NonNull private final Bundle mExtras; 1600 @Nullable private ArrayList<RemoteInput> mRemoteInputs; 1601 private @SemanticAction int mSemanticAction; 1602 private boolean mIsContextual; 1603 1604 /** 1605 * Construct a new builder for {@link Action} object. 1606 * @param icon icon to show for this action 1607 * @param title the title of the action 1608 * @param intent the {@link PendingIntent} to fire when users trigger this action 1609 */ 1610 @Deprecated Builder(int icon, CharSequence title, PendingIntent intent)1611 public Builder(int icon, CharSequence title, PendingIntent intent) { 1612 this(Icon.createWithResource("", icon), title, intent); 1613 } 1614 1615 /** 1616 * Construct a new builder for {@link Action} object. 1617 * @param icon icon to show for this action 1618 * @param title the title of the action 1619 * @param intent the {@link PendingIntent} to fire when users trigger this action 1620 */ Builder(Icon icon, CharSequence title, PendingIntent intent)1621 public Builder(Icon icon, CharSequence title, PendingIntent intent) { 1622 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE); 1623 } 1624 1625 /** 1626 * Construct a new builder for {@link Action} object using the fields from an 1627 * {@link Action}. 1628 * @param action the action to read fields from. 1629 */ Builder(Action action)1630 public Builder(Action action) { 1631 this(action.getIcon(), action.title, action.actionIntent, 1632 new Bundle(action.mExtras), action.getRemoteInputs(), 1633 action.getAllowGeneratedReplies(), action.getSemanticAction()); 1634 } 1635 Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction)1636 private Builder(@Nullable Icon icon, @Nullable CharSequence title, 1637 @Nullable PendingIntent intent, @NonNull Bundle extras, 1638 @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1639 @SemanticAction int semanticAction) { 1640 mIcon = icon; 1641 mTitle = title; 1642 mIntent = intent; 1643 mExtras = extras; 1644 if (remoteInputs != null) { 1645 mRemoteInputs = new ArrayList<RemoteInput>(remoteInputs.length); 1646 Collections.addAll(mRemoteInputs, remoteInputs); 1647 } 1648 mAllowGeneratedReplies = allowGeneratedReplies; 1649 mSemanticAction = semanticAction; 1650 } 1651 1652 /** 1653 * Merge additional metadata into this builder. 1654 * 1655 * <p>Values within the Bundle will replace existing extras values in this Builder. 1656 * 1657 * @see Notification.Action#extras 1658 */ 1659 @NonNull addExtras(Bundle extras)1660 public Builder addExtras(Bundle extras) { 1661 if (extras != null) { 1662 mExtras.putAll(extras); 1663 } 1664 return this; 1665 } 1666 1667 /** 1668 * Get the metadata Bundle used by this Builder. 1669 * 1670 * <p>The returned Bundle is shared with this Builder. 1671 */ 1672 @NonNull getExtras()1673 public Bundle getExtras() { 1674 return mExtras; 1675 } 1676 1677 /** 1678 * Add an input to be collected from the user when this action is sent. 1679 * Response values can be retrieved from the fired intent by using the 1680 * {@link RemoteInput#getResultsFromIntent} function. 1681 * @param remoteInput a {@link RemoteInput} to add to the action 1682 * @return this object for method chaining 1683 */ 1684 @NonNull addRemoteInput(RemoteInput remoteInput)1685 public Builder addRemoteInput(RemoteInput remoteInput) { 1686 if (mRemoteInputs == null) { 1687 mRemoteInputs = new ArrayList<RemoteInput>(); 1688 } 1689 mRemoteInputs.add(remoteInput); 1690 return this; 1691 } 1692 1693 /** 1694 * Set whether the platform should automatically generate possible replies to add to 1695 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 1696 * {@link RemoteInput}, this has no effect. 1697 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 1698 * otherwise 1699 * @return this object for method chaining 1700 * The default value is {@code true} 1701 */ 1702 @NonNull setAllowGeneratedReplies(boolean allowGeneratedReplies)1703 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 1704 mAllowGeneratedReplies = allowGeneratedReplies; 1705 return this; 1706 } 1707 1708 /** 1709 * Sets the {@code SemanticAction} for this {@link Action}. A 1710 * {@code SemanticAction} denotes what an {@link Action}'s 1711 * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). 1712 * @param semanticAction a SemanticAction defined within {@link Action} with 1713 * {@code SEMANTIC_ACTION_} prefixes 1714 * @return this object for method chaining 1715 */ 1716 @NonNull setSemanticAction(@emanticAction int semanticAction)1717 public Builder setSemanticAction(@SemanticAction int semanticAction) { 1718 mSemanticAction = semanticAction; 1719 return this; 1720 } 1721 1722 /** 1723 * Sets whether this {@link Action} is a contextual action, i.e. whether the action is 1724 * dependent on the notification message body. An example of a contextual action could 1725 * be an action opening a map application with an address shown in the notification. 1726 */ 1727 @NonNull setContextual(boolean isContextual)1728 public Builder setContextual(boolean isContextual) { 1729 mIsContextual = isContextual; 1730 return this; 1731 } 1732 1733 /** 1734 * Apply an extender to this action builder. Extenders may be used to add 1735 * metadata or change options on this builder. 1736 */ 1737 @NonNull extend(Extender extender)1738 public Builder extend(Extender extender) { 1739 extender.extend(this); 1740 return this; 1741 } 1742 1743 /** 1744 * Throws an NPE if we are building a contextual action missing one of the fields 1745 * necessary to display the action. 1746 */ checkContextualActionNullFields()1747 private void checkContextualActionNullFields() { 1748 if (!mIsContextual) return; 1749 1750 if (mIcon == null) { 1751 throw new NullPointerException("Contextual Actions must contain a valid icon"); 1752 } 1753 1754 if (mIntent == null) { 1755 throw new NullPointerException( 1756 "Contextual Actions must contain a valid PendingIntent"); 1757 } 1758 } 1759 1760 /** 1761 * Combine all of the options that have been set and return a new {@link Action} 1762 * object. 1763 * @return the built action 1764 */ 1765 @NonNull build()1766 public Action build() { 1767 checkContextualActionNullFields(); 1768 1769 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>(); 1770 RemoteInput[] previousDataInputs = 1771 (RemoteInput[]) mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS); 1772 if (previousDataInputs != null) { 1773 for (RemoteInput input : previousDataInputs) { 1774 dataOnlyInputs.add(input); 1775 } 1776 } 1777 List<RemoteInput> textInputs = new ArrayList<>(); 1778 if (mRemoteInputs != null) { 1779 for (RemoteInput input : mRemoteInputs) { 1780 if (input.isDataOnly()) { 1781 dataOnlyInputs.add(input); 1782 } else { 1783 textInputs.add(input); 1784 } 1785 } 1786 } 1787 if (!dataOnlyInputs.isEmpty()) { 1788 RemoteInput[] dataInputsArr = 1789 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 1790 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); 1791 } 1792 RemoteInput[] textInputsArr = textInputs.isEmpty() 1793 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 1794 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 1795 mAllowGeneratedReplies, mSemanticAction, mIsContextual); 1796 } 1797 } 1798 1799 @Override clone()1800 public Action clone() { 1801 return new Action( 1802 getIcon(), 1803 title, 1804 actionIntent, // safe to alias 1805 mExtras == null ? new Bundle() : new Bundle(mExtras), 1806 getRemoteInputs(), 1807 getAllowGeneratedReplies(), 1808 getSemanticAction(), 1809 isContextual()); 1810 } 1811 1812 @Override describeContents()1813 public int describeContents() { 1814 return 0; 1815 } 1816 1817 @Override writeToParcel(Parcel out, int flags)1818 public void writeToParcel(Parcel out, int flags) { 1819 final Icon ic = getIcon(); 1820 if (ic != null) { 1821 out.writeInt(1); 1822 ic.writeToParcel(out, 0); 1823 } else { 1824 out.writeInt(0); 1825 } 1826 TextUtils.writeToParcel(title, out, flags); 1827 if (actionIntent != null) { 1828 out.writeInt(1); 1829 actionIntent.writeToParcel(out, flags); 1830 } else { 1831 out.writeInt(0); 1832 } 1833 out.writeBundle(mExtras); 1834 out.writeTypedArray(mRemoteInputs, flags); 1835 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 1836 out.writeInt(mSemanticAction); 1837 out.writeInt(mIsContextual ? 1 : 0); 1838 } 1839 1840 public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR = 1841 new Parcelable.Creator<Action>() { 1842 public Action createFromParcel(Parcel in) { 1843 return new Action(in); 1844 } 1845 public Action[] newArray(int size) { 1846 return new Action[size]; 1847 } 1848 }; 1849 1850 /** 1851 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 1852 * metadata or change options on an action builder. 1853 */ 1854 public interface Extender { 1855 /** 1856 * Apply this extender to a notification action builder. 1857 * @param builder the builder to be modified. 1858 * @return the build object for chaining. 1859 */ extend(Builder builder)1860 public Builder extend(Builder builder); 1861 } 1862 1863 /** 1864 * Wearable extender for notification actions. To add extensions to an action, 1865 * create a new {@link android.app.Notification.Action.WearableExtender} object using 1866 * the {@code WearableExtender()} constructor and apply it to a 1867 * {@link android.app.Notification.Action.Builder} using 1868 * {@link android.app.Notification.Action.Builder#extend}. 1869 * 1870 * <pre class="prettyprint"> 1871 * Notification.Action action = new Notification.Action.Builder( 1872 * R.drawable.archive_all, "Archive all", actionIntent) 1873 * .extend(new Notification.Action.WearableExtender() 1874 * .setAvailableOffline(false)) 1875 * .build();</pre> 1876 */ 1877 public static final class WearableExtender implements Extender { 1878 /** Notification action extra which contains wearable extensions */ 1879 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 1880 1881 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 1882 private static final String KEY_FLAGS = "flags"; 1883 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 1884 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 1885 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 1886 1887 // Flags bitwise-ored to mFlags 1888 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 1889 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 1890 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 1891 1892 // Default value for flags integer 1893 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 1894 1895 private int mFlags = DEFAULT_FLAGS; 1896 1897 private CharSequence mInProgressLabel; 1898 private CharSequence mConfirmLabel; 1899 private CharSequence mCancelLabel; 1900 1901 /** 1902 * Create a {@link android.app.Notification.Action.WearableExtender} with default 1903 * options. 1904 */ WearableExtender()1905 public WearableExtender() { 1906 } 1907 1908 /** 1909 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 1910 * wearable options present in an existing notification action. 1911 * @param action the notification action to inspect. 1912 */ WearableExtender(Action action)1913 public WearableExtender(Action action) { 1914 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 1915 if (wearableBundle != null) { 1916 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 1917 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 1918 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 1919 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 1920 } 1921 } 1922 1923 /** 1924 * Apply wearable extensions to a notification action that is being built. This is 1925 * typically called by the {@link android.app.Notification.Action.Builder#extend} 1926 * method of {@link android.app.Notification.Action.Builder}. 1927 */ 1928 @Override extend(Action.Builder builder)1929 public Action.Builder extend(Action.Builder builder) { 1930 Bundle wearableBundle = new Bundle(); 1931 1932 if (mFlags != DEFAULT_FLAGS) { 1933 wearableBundle.putInt(KEY_FLAGS, mFlags); 1934 } 1935 if (mInProgressLabel != null) { 1936 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 1937 } 1938 if (mConfirmLabel != null) { 1939 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 1940 } 1941 if (mCancelLabel != null) { 1942 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 1943 } 1944 1945 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 1946 return builder; 1947 } 1948 1949 @Override clone()1950 public WearableExtender clone() { 1951 WearableExtender that = new WearableExtender(); 1952 that.mFlags = this.mFlags; 1953 that.mInProgressLabel = this.mInProgressLabel; 1954 that.mConfirmLabel = this.mConfirmLabel; 1955 that.mCancelLabel = this.mCancelLabel; 1956 return that; 1957 } 1958 1959 /** 1960 * Set whether this action is available when the wearable device is not connected to 1961 * a companion device. The user can still trigger this action when the wearable device is 1962 * offline, but a visual hint will indicate that the action may not be available. 1963 * Defaults to true. 1964 */ setAvailableOffline(boolean availableOffline)1965 public WearableExtender setAvailableOffline(boolean availableOffline) { 1966 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 1967 return this; 1968 } 1969 1970 /** 1971 * Get whether this action is available when the wearable device is not connected to 1972 * a companion device. The user can still trigger this action when the wearable device is 1973 * offline, but a visual hint will indicate that the action may not be available. 1974 * Defaults to true. 1975 */ isAvailableOffline()1976 public boolean isAvailableOffline() { 1977 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 1978 } 1979 setFlag(int mask, boolean value)1980 private void setFlag(int mask, boolean value) { 1981 if (value) { 1982 mFlags |= mask; 1983 } else { 1984 mFlags &= ~mask; 1985 } 1986 } 1987 1988 /** 1989 * Set a label to display while the wearable is preparing to automatically execute the 1990 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 1991 * 1992 * @param label the label to display while the action is being prepared to execute 1993 * @return this object for method chaining 1994 */ 1995 @Deprecated setInProgressLabel(CharSequence label)1996 public WearableExtender setInProgressLabel(CharSequence label) { 1997 mInProgressLabel = label; 1998 return this; 1999 } 2000 2001 /** 2002 * Get the label to display while the wearable is preparing to automatically execute 2003 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2004 * 2005 * @return the label to display while the action is being prepared to execute 2006 */ 2007 @Deprecated getInProgressLabel()2008 public CharSequence getInProgressLabel() { 2009 return mInProgressLabel; 2010 } 2011 2012 /** 2013 * Set a label to display to confirm that the action should be executed. 2014 * This is usually an imperative verb like "Send". 2015 * 2016 * @param label the label to confirm the action should be executed 2017 * @return this object for method chaining 2018 */ 2019 @Deprecated setConfirmLabel(CharSequence label)2020 public WearableExtender setConfirmLabel(CharSequence label) { 2021 mConfirmLabel = label; 2022 return this; 2023 } 2024 2025 /** 2026 * Get the label to display to confirm that the action should be executed. 2027 * This is usually an imperative verb like "Send". 2028 * 2029 * @return the label to confirm the action should be executed 2030 */ 2031 @Deprecated getConfirmLabel()2032 public CharSequence getConfirmLabel() { 2033 return mConfirmLabel; 2034 } 2035 2036 /** 2037 * Set a label to display to cancel the action. 2038 * This is usually an imperative verb, like "Cancel". 2039 * 2040 * @param label the label to display to cancel the action 2041 * @return this object for method chaining 2042 */ 2043 @Deprecated setCancelLabel(CharSequence label)2044 public WearableExtender setCancelLabel(CharSequence label) { 2045 mCancelLabel = label; 2046 return this; 2047 } 2048 2049 /** 2050 * Get the label to display to cancel the action. 2051 * This is usually an imperative verb like "Cancel". 2052 * 2053 * @return the label to display to cancel the action 2054 */ 2055 @Deprecated getCancelLabel()2056 public CharSequence getCancelLabel() { 2057 return mCancelLabel; 2058 } 2059 2060 /** 2061 * Set a hint that this Action will launch an {@link Activity} directly, telling the 2062 * platform that it can generate the appropriate transitions. 2063 * @param hintLaunchesActivity {@code true} if the content intent will launch 2064 * an activity and transitions should be generated, false otherwise. 2065 * @return this object for method chaining 2066 */ setHintLaunchesActivity( boolean hintLaunchesActivity)2067 public WearableExtender setHintLaunchesActivity( 2068 boolean hintLaunchesActivity) { 2069 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 2070 return this; 2071 } 2072 2073 /** 2074 * Get a hint that this Action will launch an {@link Activity} directly, telling the 2075 * platform that it can generate the appropriate transitions 2076 * @return {@code true} if the content intent will launch an activity and transitions 2077 * should be generated, false otherwise. The default value is {@code false} if this was 2078 * never set. 2079 */ getHintLaunchesActivity()2080 public boolean getHintLaunchesActivity() { 2081 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 2082 } 2083 2084 /** 2085 * Set a hint that this Action should be displayed inline. 2086 * 2087 * @param hintDisplayInline {@code true} if action should be displayed inline, false 2088 * otherwise 2089 * @return this object for method chaining 2090 */ setHintDisplayActionInline( boolean hintDisplayInline)2091 public WearableExtender setHintDisplayActionInline( 2092 boolean hintDisplayInline) { 2093 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 2094 return this; 2095 } 2096 2097 /** 2098 * Get a hint that this Action should be displayed inline. 2099 * 2100 * @return {@code true} if the Action should be displayed inline, {@code false} 2101 * otherwise. The default value is {@code false} if this was never set. 2102 */ getHintDisplayActionInline()2103 public boolean getHintDisplayActionInline() { 2104 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 2105 } 2106 } 2107 2108 /** 2109 * Provides meaning to an {@link Action} that hints at what the associated 2110 * {@link PendingIntent} will do. For example, an {@link Action} with a 2111 * {@link PendingIntent} that replies to a text message notification may have the 2112 * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. 2113 * 2114 * @hide 2115 */ 2116 @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { 2117 SEMANTIC_ACTION_NONE, 2118 SEMANTIC_ACTION_REPLY, 2119 SEMANTIC_ACTION_MARK_AS_READ, 2120 SEMANTIC_ACTION_MARK_AS_UNREAD, 2121 SEMANTIC_ACTION_DELETE, 2122 SEMANTIC_ACTION_ARCHIVE, 2123 SEMANTIC_ACTION_MUTE, 2124 SEMANTIC_ACTION_UNMUTE, 2125 SEMANTIC_ACTION_THUMBS_UP, 2126 SEMANTIC_ACTION_THUMBS_DOWN, 2127 SEMANTIC_ACTION_CALL 2128 }) 2129 @Retention(RetentionPolicy.SOURCE) 2130 public @interface SemanticAction {} 2131 } 2132 2133 /** 2134 * Array of all {@link Action} structures attached to this notification by 2135 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 2136 * {@link android.service.notification.NotificationListenerService} that provide an alternative 2137 * interface for invoking actions. 2138 */ 2139 public Action[] actions; 2140 2141 /** 2142 * Replacement version of this notification whose content will be shown 2143 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 2144 * and {@link #VISIBILITY_PUBLIC}. 2145 */ 2146 public Notification publicVersion; 2147 2148 /** 2149 * Constructs a Notification object with default values. 2150 * You might want to consider using {@link Builder} instead. 2151 */ Notification()2152 public Notification() 2153 { 2154 this.when = System.currentTimeMillis(); 2155 this.creationTime = System.currentTimeMillis(); 2156 this.priority = PRIORITY_DEFAULT; 2157 } 2158 2159 /** 2160 * @hide 2161 */ 2162 @UnsupportedAppUsage Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2163 public Notification(Context context, int icon, CharSequence tickerText, long when, 2164 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 2165 { 2166 new Builder(context) 2167 .setWhen(when) 2168 .setSmallIcon(icon) 2169 .setTicker(tickerText) 2170 .setContentTitle(contentTitle) 2171 .setContentText(contentText) 2172 .setContentIntent(PendingIntent.getActivity(context, 0, contentIntent, 0)) 2173 .buildInto(this); 2174 } 2175 2176 /** 2177 * Constructs a Notification object with the information needed to 2178 * have a status bar icon without the standard expanded view. 2179 * 2180 * @param icon The resource id of the icon to put in the status bar. 2181 * @param tickerText The text that flows by in the status bar when the notification first 2182 * activates. 2183 * @param when The time to show in the time field. In the System.currentTimeMillis 2184 * timebase. 2185 * 2186 * @deprecated Use {@link Builder} instead. 2187 */ 2188 @Deprecated Notification(int icon, CharSequence tickerText, long when)2189 public Notification(int icon, CharSequence tickerText, long when) 2190 { 2191 this.icon = icon; 2192 this.tickerText = tickerText; 2193 this.when = when; 2194 this.creationTime = System.currentTimeMillis(); 2195 } 2196 2197 /** 2198 * Unflatten the notification from a parcel. 2199 */ 2200 @SuppressWarnings("unchecked") Notification(Parcel parcel)2201 public Notification(Parcel parcel) { 2202 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 2203 // intents in extras are always written as the last entry. 2204 readFromParcelImpl(parcel); 2205 // Must be read last! 2206 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 2207 } 2208 readFromParcelImpl(Parcel parcel)2209 private void readFromParcelImpl(Parcel parcel) 2210 { 2211 int version = parcel.readInt(); 2212 2213 mWhitelistToken = parcel.readStrongBinder(); 2214 if (mWhitelistToken == null) { 2215 mWhitelistToken = processWhitelistToken; 2216 } 2217 // Propagate this token to all pending intents that are unmarshalled from the parcel. 2218 parcel.setClassCookie(PendingIntent.class, mWhitelistToken); 2219 2220 when = parcel.readLong(); 2221 creationTime = parcel.readLong(); 2222 if (parcel.readInt() != 0) { 2223 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 2224 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 2225 icon = mSmallIcon.getResId(); 2226 } 2227 } 2228 number = parcel.readInt(); 2229 if (parcel.readInt() != 0) { 2230 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2231 } 2232 if (parcel.readInt() != 0) { 2233 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2234 } 2235 if (parcel.readInt() != 0) { 2236 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2237 } 2238 if (parcel.readInt() != 0) { 2239 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 2240 } 2241 if (parcel.readInt() != 0) { 2242 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 2243 } 2244 if (parcel.readInt() != 0) { 2245 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 2246 } 2247 defaults = parcel.readInt(); 2248 flags = parcel.readInt(); 2249 if (parcel.readInt() != 0) { 2250 sound = Uri.CREATOR.createFromParcel(parcel); 2251 } 2252 2253 audioStreamType = parcel.readInt(); 2254 if (parcel.readInt() != 0) { 2255 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 2256 } 2257 vibrate = parcel.createLongArray(); 2258 ledARGB = parcel.readInt(); 2259 ledOnMS = parcel.readInt(); 2260 ledOffMS = parcel.readInt(); 2261 iconLevel = parcel.readInt(); 2262 2263 if (parcel.readInt() != 0) { 2264 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2265 } 2266 2267 priority = parcel.readInt(); 2268 2269 category = parcel.readString(); 2270 2271 mGroupKey = parcel.readString(); 2272 2273 mSortKey = parcel.readString(); 2274 2275 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 2276 fixDuplicateExtras(); 2277 2278 actions = parcel.createTypedArray(Action.CREATOR); // may be null 2279 2280 if (parcel.readInt() != 0) { 2281 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2282 } 2283 2284 if (parcel.readInt() != 0) { 2285 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2286 } 2287 2288 visibility = parcel.readInt(); 2289 2290 if (parcel.readInt() != 0) { 2291 publicVersion = Notification.CREATOR.createFromParcel(parcel); 2292 } 2293 2294 color = parcel.readInt(); 2295 2296 if (parcel.readInt() != 0) { 2297 mChannelId = parcel.readString(); 2298 } 2299 mTimeout = parcel.readLong(); 2300 2301 if (parcel.readInt() != 0) { 2302 mShortcutId = parcel.readString(); 2303 } 2304 2305 if (parcel.readInt() != 0) { 2306 mLocusId = LocusId.CREATOR.createFromParcel(parcel); 2307 } 2308 2309 mBadgeIcon = parcel.readInt(); 2310 2311 if (parcel.readInt() != 0) { 2312 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2313 } 2314 2315 mGroupAlertBehavior = parcel.readInt(); 2316 if (parcel.readInt() != 0) { 2317 mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel); 2318 } 2319 2320 mAllowSystemGeneratedContextualActions = parcel.readBoolean(); 2321 } 2322 2323 @Override clone()2324 public Notification clone() { 2325 Notification that = new Notification(); 2326 cloneInto(that, true); 2327 return that; 2328 } 2329 2330 /** 2331 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 2332 * of this into that. 2333 * @hide 2334 */ cloneInto(Notification that, boolean heavy)2335 public void cloneInto(Notification that, boolean heavy) { 2336 that.mWhitelistToken = this.mWhitelistToken; 2337 that.when = this.when; 2338 that.creationTime = this.creationTime; 2339 that.mSmallIcon = this.mSmallIcon; 2340 that.number = this.number; 2341 2342 // PendingIntents are global, so there's no reason (or way) to clone them. 2343 that.contentIntent = this.contentIntent; 2344 that.deleteIntent = this.deleteIntent; 2345 that.fullScreenIntent = this.fullScreenIntent; 2346 2347 if (this.tickerText != null) { 2348 that.tickerText = this.tickerText.toString(); 2349 } 2350 if (heavy && this.tickerView != null) { 2351 that.tickerView = this.tickerView.clone(); 2352 } 2353 if (heavy && this.contentView != null) { 2354 that.contentView = this.contentView.clone(); 2355 } 2356 if (heavy && this.mLargeIcon != null) { 2357 that.mLargeIcon = this.mLargeIcon; 2358 } 2359 that.iconLevel = this.iconLevel; 2360 that.sound = this.sound; // android.net.Uri is immutable 2361 that.audioStreamType = this.audioStreamType; 2362 if (this.audioAttributes != null) { 2363 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 2364 } 2365 2366 final long[] vibrate = this.vibrate; 2367 if (vibrate != null) { 2368 final int N = vibrate.length; 2369 final long[] vib = that.vibrate = new long[N]; 2370 System.arraycopy(vibrate, 0, vib, 0, N); 2371 } 2372 2373 that.ledARGB = this.ledARGB; 2374 that.ledOnMS = this.ledOnMS; 2375 that.ledOffMS = this.ledOffMS; 2376 that.defaults = this.defaults; 2377 2378 that.flags = this.flags; 2379 2380 that.priority = this.priority; 2381 2382 that.category = this.category; 2383 2384 that.mGroupKey = this.mGroupKey; 2385 2386 that.mSortKey = this.mSortKey; 2387 2388 if (this.extras != null) { 2389 try { 2390 that.extras = new Bundle(this.extras); 2391 // will unparcel 2392 that.extras.size(); 2393 } catch (BadParcelableException e) { 2394 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 2395 that.extras = null; 2396 } 2397 } 2398 2399 if (!ArrayUtils.isEmpty(allPendingIntents)) { 2400 that.allPendingIntents = new ArraySet<>(allPendingIntents); 2401 } 2402 2403 if (this.actions != null) { 2404 that.actions = new Action[this.actions.length]; 2405 for(int i=0; i<this.actions.length; i++) { 2406 if ( this.actions[i] != null) { 2407 that.actions[i] = this.actions[i].clone(); 2408 } 2409 } 2410 } 2411 2412 if (heavy && this.bigContentView != null) { 2413 that.bigContentView = this.bigContentView.clone(); 2414 } 2415 2416 if (heavy && this.headsUpContentView != null) { 2417 that.headsUpContentView = this.headsUpContentView.clone(); 2418 } 2419 2420 that.visibility = this.visibility; 2421 2422 if (this.publicVersion != null) { 2423 that.publicVersion = new Notification(); 2424 this.publicVersion.cloneInto(that.publicVersion, heavy); 2425 } 2426 2427 that.color = this.color; 2428 2429 that.mChannelId = this.mChannelId; 2430 that.mTimeout = this.mTimeout; 2431 that.mShortcutId = this.mShortcutId; 2432 that.mLocusId = this.mLocusId; 2433 that.mBadgeIcon = this.mBadgeIcon; 2434 that.mSettingsText = this.mSettingsText; 2435 that.mGroupAlertBehavior = this.mGroupAlertBehavior; 2436 that.mBubbleMetadata = this.mBubbleMetadata; 2437 that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; 2438 2439 if (!heavy) { 2440 that.lightenPayload(); // will clean out extras 2441 } 2442 } 2443 2444 /** 2445 * Note all {@link Uri} that are referenced internally, with the expectation 2446 * that Uri permission grants will need to be issued to ensure the recipient 2447 * of this object is able to render its contents. 2448 * 2449 * @hide 2450 */ visitUris(@onNull Consumer<Uri> visitor)2451 public void visitUris(@NonNull Consumer<Uri> visitor) { 2452 visitor.accept(sound); 2453 2454 if (tickerView != null) tickerView.visitUris(visitor); 2455 if (contentView != null) contentView.visitUris(visitor); 2456 if (bigContentView != null) bigContentView.visitUris(visitor); 2457 if (headsUpContentView != null) headsUpContentView.visitUris(visitor); 2458 2459 if (extras != null) { 2460 visitor.accept(extras.getParcelable(EXTRA_AUDIO_CONTENTS_URI)); 2461 if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 2462 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); 2463 } 2464 } 2465 2466 if (MessagingStyle.class.equals(getNotificationStyle()) && extras != null) { 2467 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 2468 if (!ArrayUtils.isEmpty(messages)) { 2469 for (MessagingStyle.Message message : MessagingStyle.Message 2470 .getMessagesFromBundleArray(messages)) { 2471 visitor.accept(message.getDataUri()); 2472 } 2473 } 2474 2475 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 2476 if (!ArrayUtils.isEmpty(historic)) { 2477 for (MessagingStyle.Message message : MessagingStyle.Message 2478 .getMessagesFromBundleArray(historic)) { 2479 visitor.accept(message.getDataUri()); 2480 } 2481 } 2482 } 2483 } 2484 2485 /** 2486 * Removes heavyweight parts of the Notification object for archival or for sending to 2487 * listeners when the full contents are not necessary. 2488 * @hide 2489 */ lightenPayload()2490 public final void lightenPayload() { 2491 tickerView = null; 2492 contentView = null; 2493 bigContentView = null; 2494 headsUpContentView = null; 2495 mLargeIcon = null; 2496 if (extras != null && !extras.isEmpty()) { 2497 final Set<String> keyset = extras.keySet(); 2498 final int N = keyset.size(); 2499 final String[] keys = keyset.toArray(new String[N]); 2500 for (int i=0; i<N; i++) { 2501 final String key = keys[i]; 2502 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) { 2503 continue; 2504 } 2505 final Object obj = extras.get(key); 2506 if (obj != null && 2507 ( obj instanceof Parcelable 2508 || obj instanceof Parcelable[] 2509 || obj instanceof SparseArray 2510 || obj instanceof ArrayList)) { 2511 extras.remove(key); 2512 } 2513 } 2514 } 2515 } 2516 2517 /** 2518 * Make sure this CharSequence is safe to put into a bundle, which basically 2519 * means it had better not be some custom Parcelable implementation. 2520 * @hide 2521 */ safeCharSequence(CharSequence cs)2522 public static CharSequence safeCharSequence(CharSequence cs) { 2523 if (cs == null) return cs; 2524 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 2525 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 2526 } 2527 if (cs instanceof Parcelable) { 2528 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 2529 + " instance is a custom Parcelable and not allowed in Notification"); 2530 return cs.toString(); 2531 } 2532 return removeTextSizeSpans(cs); 2533 } 2534 removeTextSizeSpans(CharSequence charSequence)2535 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 2536 if (charSequence instanceof Spanned) { 2537 Spanned ss = (Spanned) charSequence; 2538 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 2539 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 2540 for (Object span : spans) { 2541 Object resultSpan = span; 2542 if (resultSpan instanceof CharacterStyle) { 2543 resultSpan = ((CharacterStyle) span).getUnderlying(); 2544 } 2545 if (resultSpan instanceof TextAppearanceSpan) { 2546 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 2547 resultSpan = new TextAppearanceSpan( 2548 originalSpan.getFamily(), 2549 originalSpan.getTextStyle(), 2550 -1, 2551 originalSpan.getTextColor(), 2552 originalSpan.getLinkTextColor()); 2553 } else if (resultSpan instanceof RelativeSizeSpan 2554 || resultSpan instanceof AbsoluteSizeSpan) { 2555 continue; 2556 } else { 2557 resultSpan = span; 2558 } 2559 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 2560 ss.getSpanFlags(span)); 2561 } 2562 return builder; 2563 } 2564 return charSequence; 2565 } 2566 describeContents()2567 public int describeContents() { 2568 return 0; 2569 } 2570 2571 /** 2572 * Flatten this notification into a parcel. 2573 */ writeToParcel(Parcel parcel, int flags)2574 public void writeToParcel(Parcel parcel, int flags) { 2575 // We need to mark all pending intents getting into the notification 2576 // system as being put there to later allow the notification ranker 2577 // to launch them and by doing so add the app to the battery saver white 2578 // list for a short period of time. The problem is that the system 2579 // cannot look into the extras as there may be parcelables there that 2580 // the platform does not know how to handle. To go around that we have 2581 // an explicit list of the pending intents in the extras bundle. 2582 final boolean collectPendingIntents = (allPendingIntents == null); 2583 if (collectPendingIntents) { 2584 PendingIntent.setOnMarshaledListener( 2585 (PendingIntent intent, Parcel out, int outFlags) -> { 2586 if (parcel == out) { 2587 if (allPendingIntents == null) { 2588 allPendingIntents = new ArraySet<>(); 2589 } 2590 allPendingIntents.add(intent); 2591 } 2592 }); 2593 } 2594 try { 2595 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 2596 // want to intercept all pending events written to the parcel. 2597 writeToParcelImpl(parcel, flags); 2598 // Must be written last! 2599 parcel.writeArraySet(allPendingIntents); 2600 } finally { 2601 if (collectPendingIntents) { 2602 PendingIntent.setOnMarshaledListener(null); 2603 } 2604 } 2605 } 2606 writeToParcelImpl(Parcel parcel, int flags)2607 private void writeToParcelImpl(Parcel parcel, int flags) { 2608 parcel.writeInt(1); 2609 2610 parcel.writeStrongBinder(mWhitelistToken); 2611 parcel.writeLong(when); 2612 parcel.writeLong(creationTime); 2613 if (mSmallIcon == null && icon != 0) { 2614 // you snuck an icon in here without using the builder; let's try to keep it 2615 mSmallIcon = Icon.createWithResource("", icon); 2616 } 2617 if (mSmallIcon != null) { 2618 parcel.writeInt(1); 2619 mSmallIcon.writeToParcel(parcel, 0); 2620 } else { 2621 parcel.writeInt(0); 2622 } 2623 parcel.writeInt(number); 2624 if (contentIntent != null) { 2625 parcel.writeInt(1); 2626 contentIntent.writeToParcel(parcel, 0); 2627 } else { 2628 parcel.writeInt(0); 2629 } 2630 if (deleteIntent != null) { 2631 parcel.writeInt(1); 2632 deleteIntent.writeToParcel(parcel, 0); 2633 } else { 2634 parcel.writeInt(0); 2635 } 2636 if (tickerText != null) { 2637 parcel.writeInt(1); 2638 TextUtils.writeToParcel(tickerText, parcel, flags); 2639 } else { 2640 parcel.writeInt(0); 2641 } 2642 if (tickerView != null) { 2643 parcel.writeInt(1); 2644 tickerView.writeToParcel(parcel, 0); 2645 } else { 2646 parcel.writeInt(0); 2647 } 2648 if (contentView != null) { 2649 parcel.writeInt(1); 2650 contentView.writeToParcel(parcel, 0); 2651 } else { 2652 parcel.writeInt(0); 2653 } 2654 if (mLargeIcon == null && largeIcon != null) { 2655 // you snuck an icon in here without using the builder; let's try to keep it 2656 mLargeIcon = Icon.createWithBitmap(largeIcon); 2657 } 2658 if (mLargeIcon != null) { 2659 parcel.writeInt(1); 2660 mLargeIcon.writeToParcel(parcel, 0); 2661 } else { 2662 parcel.writeInt(0); 2663 } 2664 2665 parcel.writeInt(defaults); 2666 parcel.writeInt(this.flags); 2667 2668 if (sound != null) { 2669 parcel.writeInt(1); 2670 sound.writeToParcel(parcel, 0); 2671 } else { 2672 parcel.writeInt(0); 2673 } 2674 parcel.writeInt(audioStreamType); 2675 2676 if (audioAttributes != null) { 2677 parcel.writeInt(1); 2678 audioAttributes.writeToParcel(parcel, 0); 2679 } else { 2680 parcel.writeInt(0); 2681 } 2682 2683 parcel.writeLongArray(vibrate); 2684 parcel.writeInt(ledARGB); 2685 parcel.writeInt(ledOnMS); 2686 parcel.writeInt(ledOffMS); 2687 parcel.writeInt(iconLevel); 2688 2689 if (fullScreenIntent != null) { 2690 parcel.writeInt(1); 2691 fullScreenIntent.writeToParcel(parcel, 0); 2692 } else { 2693 parcel.writeInt(0); 2694 } 2695 2696 parcel.writeInt(priority); 2697 2698 parcel.writeString(category); 2699 2700 parcel.writeString(mGroupKey); 2701 2702 parcel.writeString(mSortKey); 2703 2704 parcel.writeBundle(extras); // null ok 2705 2706 parcel.writeTypedArray(actions, 0); // null ok 2707 2708 if (bigContentView != null) { 2709 parcel.writeInt(1); 2710 bigContentView.writeToParcel(parcel, 0); 2711 } else { 2712 parcel.writeInt(0); 2713 } 2714 2715 if (headsUpContentView != null) { 2716 parcel.writeInt(1); 2717 headsUpContentView.writeToParcel(parcel, 0); 2718 } else { 2719 parcel.writeInt(0); 2720 } 2721 2722 parcel.writeInt(visibility); 2723 2724 if (publicVersion != null) { 2725 parcel.writeInt(1); 2726 publicVersion.writeToParcel(parcel, 0); 2727 } else { 2728 parcel.writeInt(0); 2729 } 2730 2731 parcel.writeInt(color); 2732 2733 if (mChannelId != null) { 2734 parcel.writeInt(1); 2735 parcel.writeString(mChannelId); 2736 } else { 2737 parcel.writeInt(0); 2738 } 2739 parcel.writeLong(mTimeout); 2740 2741 if (mShortcutId != null) { 2742 parcel.writeInt(1); 2743 parcel.writeString(mShortcutId); 2744 } else { 2745 parcel.writeInt(0); 2746 } 2747 2748 if (mLocusId != null) { 2749 parcel.writeInt(1); 2750 mLocusId.writeToParcel(parcel, 0); 2751 } else { 2752 parcel.writeInt(0); 2753 } 2754 2755 parcel.writeInt(mBadgeIcon); 2756 2757 if (mSettingsText != null) { 2758 parcel.writeInt(1); 2759 TextUtils.writeToParcel(mSettingsText, parcel, flags); 2760 } else { 2761 parcel.writeInt(0); 2762 } 2763 2764 parcel.writeInt(mGroupAlertBehavior); 2765 2766 if (mBubbleMetadata != null) { 2767 parcel.writeInt(1); 2768 mBubbleMetadata.writeToParcel(parcel, 0); 2769 } else { 2770 parcel.writeInt(0); 2771 } 2772 2773 parcel.writeBoolean(mAllowSystemGeneratedContextualActions); 2774 2775 // mUsesStandardHeader is not written because it should be recomputed in listeners 2776 } 2777 2778 /** 2779 * Parcelable.Creator that instantiates Notification objects 2780 */ 2781 public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR 2782 = new Parcelable.Creator<Notification>() 2783 { 2784 public Notification createFromParcel(Parcel parcel) 2785 { 2786 return new Notification(parcel); 2787 } 2788 2789 public Notification[] newArray(int size) 2790 { 2791 return new Notification[size]; 2792 } 2793 }; 2794 2795 /** 2796 * @hide 2797 */ areActionsVisiblyDifferent(Notification first, Notification second)2798 public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { 2799 Notification.Action[] firstAs = first.actions; 2800 Notification.Action[] secondAs = second.actions; 2801 if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { 2802 return true; 2803 } 2804 if (firstAs != null && secondAs != null) { 2805 if (firstAs.length != secondAs.length) { 2806 return true; 2807 } 2808 for (int i = 0; i < firstAs.length; i++) { 2809 if (!Objects.equals(String.valueOf(firstAs[i].title), 2810 String.valueOf(secondAs[i].title))) { 2811 return true; 2812 } 2813 RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); 2814 RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); 2815 if (firstRs == null) { 2816 firstRs = new RemoteInput[0]; 2817 } 2818 if (secondRs == null) { 2819 secondRs = new RemoteInput[0]; 2820 } 2821 if (firstRs.length != secondRs.length) { 2822 return true; 2823 } 2824 for (int j = 0; j < firstRs.length; j++) { 2825 if (!Objects.equals(String.valueOf(firstRs[j].getLabel()), 2826 String.valueOf(secondRs[j].getLabel()))) { 2827 return true; 2828 } 2829 } 2830 } 2831 } 2832 return false; 2833 } 2834 2835 /** 2836 * @hide 2837 */ areStyledNotificationsVisiblyDifferent(Builder first, Builder second)2838 public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { 2839 if (first.getStyle() == null) { 2840 return second.getStyle() != null; 2841 } 2842 if (second.getStyle() == null) { 2843 return true; 2844 } 2845 return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); 2846 } 2847 2848 /** 2849 * @hide 2850 */ areRemoteViewsChanged(Builder first, Builder second)2851 public static boolean areRemoteViewsChanged(Builder first, Builder second) { 2852 if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) { 2853 return true; 2854 } 2855 2856 if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) { 2857 return true; 2858 } 2859 if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) { 2860 return true; 2861 } 2862 if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) { 2863 return true; 2864 } 2865 2866 return false; 2867 } 2868 areRemoteViewsChanged(RemoteViews first, RemoteViews second)2869 private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) { 2870 if (first == null && second == null) { 2871 return false; 2872 } 2873 if (first == null && second != null || first != null && second == null) { 2874 return true; 2875 } 2876 2877 if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) { 2878 return true; 2879 } 2880 2881 if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) { 2882 return true; 2883 } 2884 2885 return false; 2886 } 2887 2888 /** 2889 * Parcelling creates multiple copies of objects in {@code extras}. Fix them. 2890 * <p> 2891 * For backwards compatibility {@code extras} holds some references to "real" member data such 2892 * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly 2893 * fine as long as the object stays in one process. 2894 * <p> 2895 * However, once the notification goes into a parcel each reference gets marshalled separately, 2896 * wasting memory. Especially with large images on Auto and TV, this is worth fixing. 2897 */ fixDuplicateExtras()2898 private void fixDuplicateExtras() { 2899 if (extras != null) { 2900 fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); 2901 } 2902 } 2903 2904 /** 2905 * If we find an extra that's exactly the same as one of the "real" fields but refers to a 2906 * separate object, replace it with the field's version to avoid holding duplicate copies. 2907 */ fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)2908 private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { 2909 if (original != null && extras.getParcelable(extraName) != null) { 2910 extras.putParcelable(extraName, original); 2911 } 2912 } 2913 2914 /** 2915 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 2916 * layout. 2917 * 2918 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 2919 * in the view.</p> 2920 * @param context The context for your application / activity. 2921 * @param contentTitle The title that goes in the expanded entry. 2922 * @param contentText The text that goes in the expanded entry. 2923 * @param contentIntent The intent to launch when the user clicks the expanded notification. 2924 * If this is an activity, it must include the 2925 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 2926 * that you take care of task management as described in the 2927 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 2928 * Stack</a> document. 2929 * 2930 * @deprecated Use {@link Builder} instead. 2931 * @removed 2932 */ 2933 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)2934 public void setLatestEventInfo(Context context, 2935 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 2936 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 2937 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 2938 new Throwable()); 2939 } 2940 2941 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 2942 extras.putBoolean(EXTRA_SHOW_WHEN, true); 2943 } 2944 2945 // ensure that any information already set directly is preserved 2946 final Notification.Builder builder = new Notification.Builder(context, this); 2947 2948 // now apply the latestEventInfo fields 2949 if (contentTitle != null) { 2950 builder.setContentTitle(contentTitle); 2951 } 2952 if (contentText != null) { 2953 builder.setContentText(contentText); 2954 } 2955 builder.setContentIntent(contentIntent); 2956 2957 builder.build(); // callers expect this notification to be ready to use 2958 } 2959 2960 /** 2961 * @hide 2962 */ addFieldsFromContext(Context context, Notification notification)2963 public static void addFieldsFromContext(Context context, Notification notification) { 2964 addFieldsFromContext(context.getApplicationInfo(), notification); 2965 } 2966 2967 /** 2968 * @hide 2969 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)2970 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 2971 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 2972 } 2973 2974 /** 2975 * @hide 2976 */ writeToProto(ProtoOutputStream proto, long fieldId)2977 public void writeToProto(ProtoOutputStream proto, long fieldId) { 2978 long token = proto.start(fieldId); 2979 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 2980 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 2981 proto.write(NotificationProto.FLAGS, this.flags); 2982 proto.write(NotificationProto.COLOR, this.color); 2983 proto.write(NotificationProto.CATEGORY, this.category); 2984 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 2985 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 2986 if (this.actions != null) { 2987 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 2988 } 2989 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 2990 proto.write(NotificationProto.VISIBILITY, this.visibility); 2991 } 2992 if (publicVersion != null) { 2993 publicVersion.writeToProto(proto, NotificationProto.PUBLIC_VERSION); 2994 } 2995 proto.end(token); 2996 } 2997 2998 @Override toString()2999 public String toString() { 3000 StringBuilder sb = new StringBuilder(); 3001 sb.append("Notification(channel="); 3002 sb.append(getChannelId()); 3003 sb.append(" pri="); 3004 sb.append(priority); 3005 sb.append(" contentView="); 3006 if (contentView != null) { 3007 sb.append(contentView.getPackage()); 3008 sb.append("/0x"); 3009 sb.append(Integer.toHexString(contentView.getLayoutId())); 3010 } else { 3011 sb.append("null"); 3012 } 3013 sb.append(" vibrate="); 3014 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3015 sb.append("default"); 3016 } else if (this.vibrate != null) { 3017 int N = this.vibrate.length-1; 3018 sb.append("["); 3019 for (int i=0; i<N; i++) { 3020 sb.append(this.vibrate[i]); 3021 sb.append(','); 3022 } 3023 if (N != -1) { 3024 sb.append(this.vibrate[N]); 3025 } 3026 sb.append("]"); 3027 } else { 3028 sb.append("null"); 3029 } 3030 sb.append(" sound="); 3031 if ((this.defaults & DEFAULT_SOUND) != 0) { 3032 sb.append("default"); 3033 } else if (this.sound != null) { 3034 sb.append(this.sound.toString()); 3035 } else { 3036 sb.append("null"); 3037 } 3038 if (this.tickerText != null) { 3039 sb.append(" tick"); 3040 } 3041 sb.append(" defaults=0x"); 3042 sb.append(Integer.toHexString(this.defaults)); 3043 sb.append(" flags=0x"); 3044 sb.append(Integer.toHexString(this.flags)); 3045 sb.append(String.format(" color=0x%08x", this.color)); 3046 if (this.category != null) { 3047 sb.append(" category="); 3048 sb.append(this.category); 3049 } 3050 if (this.mGroupKey != null) { 3051 sb.append(" groupKey="); 3052 sb.append(this.mGroupKey); 3053 } 3054 if (this.mSortKey != null) { 3055 sb.append(" sortKey="); 3056 sb.append(this.mSortKey); 3057 } 3058 if (actions != null) { 3059 sb.append(" actions="); 3060 sb.append(actions.length); 3061 } 3062 sb.append(" vis="); 3063 sb.append(visibilityToString(this.visibility)); 3064 if (this.publicVersion != null) { 3065 sb.append(" publicVersion="); 3066 sb.append(publicVersion.toString()); 3067 } 3068 if (this.mLocusId != null) { 3069 sb.append(" locusId="); 3070 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3071 } 3072 sb.append(")"); 3073 return sb.toString(); 3074 } 3075 3076 /** 3077 * {@hide} 3078 */ visibilityToString(int vis)3079 public static String visibilityToString(int vis) { 3080 switch (vis) { 3081 case VISIBILITY_PRIVATE: 3082 return "PRIVATE"; 3083 case VISIBILITY_PUBLIC: 3084 return "PUBLIC"; 3085 case VISIBILITY_SECRET: 3086 return "SECRET"; 3087 default: 3088 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3089 } 3090 } 3091 3092 /** 3093 * {@hide} 3094 */ priorityToString(@riority int pri)3095 public static String priorityToString(@Priority int pri) { 3096 switch (pri) { 3097 case PRIORITY_MIN: 3098 return "MIN"; 3099 case PRIORITY_LOW: 3100 return "LOW"; 3101 case PRIORITY_DEFAULT: 3102 return "DEFAULT"; 3103 case PRIORITY_HIGH: 3104 return "HIGH"; 3105 case PRIORITY_MAX: 3106 return "MAX"; 3107 default: 3108 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3109 } 3110 } 3111 3112 /** 3113 * @hide 3114 */ hasCompletedProgress()3115 public boolean hasCompletedProgress() { 3116 // not a progress notification; can't be complete 3117 if (!extras.containsKey(EXTRA_PROGRESS) 3118 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 3119 return false; 3120 } 3121 // many apps use max 0 for 'indeterminate'; not complete 3122 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 3123 return false; 3124 } 3125 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 3126 } 3127 3128 /** @removed */ 3129 @Deprecated getChannel()3130 public String getChannel() { 3131 return mChannelId; 3132 } 3133 3134 /** 3135 * Returns the id of the channel this notification posts to. 3136 */ getChannelId()3137 public String getChannelId() { 3138 return mChannelId; 3139 } 3140 3141 /** @removed */ 3142 @Deprecated getTimeout()3143 public long getTimeout() { 3144 return mTimeout; 3145 } 3146 3147 /** 3148 * Returns the duration from posting after which this notification should be canceled by the 3149 * system, if it's not canceled already. 3150 */ getTimeoutAfter()3151 public long getTimeoutAfter() { 3152 return mTimeout; 3153 } 3154 3155 /** 3156 * Returns what icon should be shown for this notification if it is being displayed in a 3157 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 3158 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 3159 */ getBadgeIconType()3160 public int getBadgeIconType() { 3161 return mBadgeIcon; 3162 } 3163 3164 /** 3165 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 3166 * 3167 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 3168 * notifications. 3169 */ getShortcutId()3170 public String getShortcutId() { 3171 return mShortcutId; 3172 } 3173 3174 /** 3175 * Gets the {@link LocusId} associated with this notification. 3176 * 3177 * <p>Used by the Android system to correlate objects (such as 3178 * {@link ShortcutInfo} and {@link ContentCaptureContext}). 3179 */ 3180 @Nullable getLocusId()3181 public LocusId getLocusId() { 3182 return mLocusId; 3183 } 3184 3185 /** 3186 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 3187 */ getSettingsText()3188 public CharSequence getSettingsText() { 3189 return mSettingsText; 3190 } 3191 3192 /** 3193 * Returns which type of notifications in a group are responsible for audibly alerting the 3194 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 3195 * {@link #GROUP_ALERT_SUMMARY}. 3196 */ getGroupAlertBehavior()3197 public @GroupAlertBehavior int getGroupAlertBehavior() { 3198 return mGroupAlertBehavior; 3199 } 3200 3201 /** 3202 * Returns the bubble metadata that will be used to display app content in a floating window 3203 * over the existing foreground activity. 3204 */ 3205 @Nullable getBubbleMetadata()3206 public BubbleMetadata getBubbleMetadata() { 3207 return mBubbleMetadata; 3208 } 3209 3210 /** 3211 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 3212 * for this notification. 3213 */ getAllowSystemGeneratedContextualActions()3214 public boolean getAllowSystemGeneratedContextualActions() { 3215 return mAllowSystemGeneratedContextualActions; 3216 } 3217 3218 /** 3219 * The small icon representing this notification in the status bar and content view. 3220 * 3221 * @return the small icon representing this notification. 3222 * 3223 * @see Builder#getSmallIcon() 3224 * @see Builder#setSmallIcon(Icon) 3225 */ getSmallIcon()3226 public Icon getSmallIcon() { 3227 return mSmallIcon; 3228 } 3229 3230 /** 3231 * Used when notifying to clean up legacy small icons. 3232 * @hide 3233 */ 3234 @UnsupportedAppUsage setSmallIcon(Icon icon)3235 public void setSmallIcon(Icon icon) { 3236 mSmallIcon = icon; 3237 } 3238 3239 /** 3240 * The large icon shown in this notification's content view. 3241 * @see Builder#getLargeIcon() 3242 * @see Builder#setLargeIcon(Icon) 3243 */ getLargeIcon()3244 public Icon getLargeIcon() { 3245 return mLargeIcon; 3246 } 3247 3248 /** 3249 * @hide 3250 */ 3251 @UnsupportedAppUsage isGroupSummary()3252 public boolean isGroupSummary() { 3253 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 3254 } 3255 3256 /** 3257 * @hide 3258 */ 3259 @UnsupportedAppUsage isGroupChild()3260 public boolean isGroupChild() { 3261 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 3262 } 3263 3264 /** 3265 * @hide 3266 */ suppressAlertingDueToGrouping()3267 public boolean suppressAlertingDueToGrouping() { 3268 if (isGroupSummary() 3269 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 3270 return true; 3271 } else if (isGroupChild() 3272 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 3273 return true; 3274 } 3275 return false; 3276 } 3277 3278 3279 /** 3280 * Finds and returns a remote input and its corresponding action. 3281 * 3282 * @param requiresFreeform requires the remoteinput to allow freeform or not. 3283 * @return the result pair, {@code null} if no result is found. 3284 * 3285 * @hide 3286 */ 3287 @Nullable findRemoteInputActionPair(boolean requiresFreeform)3288 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 3289 if (actions == null) { 3290 return null; 3291 } 3292 for (Notification.Action action : actions) { 3293 if (action.getRemoteInputs() == null) { 3294 continue; 3295 } 3296 RemoteInput resultRemoteInput = null; 3297 for (RemoteInput remoteInput : action.getRemoteInputs()) { 3298 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 3299 resultRemoteInput = remoteInput; 3300 } 3301 } 3302 if (resultRemoteInput != null) { 3303 return Pair.create(resultRemoteInput, action); 3304 } 3305 } 3306 return null; 3307 } 3308 3309 /** 3310 * Returns the actions that are contextual out of the actions in this notification. 3311 * 3312 * @hide 3313 */ getContextualActions()3314 public List<Notification.Action> getContextualActions() { 3315 if (actions == null) return Collections.emptyList(); 3316 3317 List<Notification.Action> contextualActions = new ArrayList<>(); 3318 for (Notification.Action action : actions) { 3319 if (action.isContextual()) { 3320 contextualActions.add(action); 3321 } 3322 } 3323 return contextualActions; 3324 } 3325 3326 /** 3327 * Builder class for {@link Notification} objects. 3328 * 3329 * Provides a convenient way to set the various fields of a {@link Notification} and generate 3330 * content views using the platform's notification layout template. If your app supports 3331 * versions of Android as old as API level 4, you can instead use 3332 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 3333 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 3334 * library</a>. 3335 * 3336 * <p>Example: 3337 * 3338 * <pre class="prettyprint"> 3339 * Notification noti = new Notification.Builder(mContext) 3340 * .setContentTitle("New mail from " + sender.toString()) 3341 * .setContentText(subject) 3342 * .setSmallIcon(R.drawable.new_mail) 3343 * .setLargeIcon(aBitmap) 3344 * .build(); 3345 * </pre> 3346 */ 3347 public static class Builder { 3348 /** 3349 * @hide 3350 */ 3351 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 3352 "android.rebuild.contentViewActionCount"; 3353 /** 3354 * @hide 3355 */ 3356 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 3357 = "android.rebuild.bigViewActionCount"; 3358 /** 3359 * @hide 3360 */ 3361 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 3362 = "android.rebuild.hudViewActionCount"; 3363 3364 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 3365 SystemProperties.getBoolean("notifications.only_title", true); 3366 3367 /** 3368 * The lightness difference that has to be added to the primary text color to obtain the 3369 * secondary text color when the background is light. 3370 */ 3371 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 3372 3373 /** 3374 * The lightness difference that has to be added to the primary text color to obtain the 3375 * secondary text color when the background is dark. 3376 * A bit less then the above value, since it looks better on dark backgrounds. 3377 */ 3378 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 3379 3380 private Context mContext; 3381 private Notification mN; 3382 private Bundle mUserExtras = new Bundle(); 3383 private Style mStyle; 3384 @UnsupportedAppUsage 3385 private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); 3386 private ArrayList<Person> mPersonList = new ArrayList<>(); 3387 private ContrastColorUtil mColorUtil; 3388 private boolean mIsLegacy; 3389 private boolean mIsLegacyInitialized; 3390 3391 /** 3392 * Caches a contrast-enhanced version of {@link #mCachedContrastColorIsFor}. 3393 */ 3394 private int mCachedContrastColor = COLOR_INVALID; 3395 private int mCachedContrastColorIsFor = COLOR_INVALID; 3396 3397 /** 3398 * A neutral color color that can be used for icons. 3399 */ 3400 private int mNeutralColor = COLOR_INVALID; 3401 3402 /** 3403 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 3404 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 3405 */ 3406 StandardTemplateParams mParams = new StandardTemplateParams(); 3407 private int mTextColorsAreForBackground = COLOR_INVALID; 3408 private int mPrimaryTextColor = COLOR_INVALID; 3409 private int mSecondaryTextColor = COLOR_INVALID; 3410 private int mBackgroundColor = COLOR_INVALID; 3411 private int mForegroundColor = COLOR_INVALID; 3412 /** 3413 * A temporary location where actions are stored. If != null the view originally has action 3414 * but doesn't have any for this inflation. 3415 */ 3416 private ArrayList<Action> mOriginalActions; 3417 private boolean mRebuildStyledRemoteViews; 3418 3419 private boolean mTintActionButtons; 3420 private boolean mInNightMode; 3421 3422 /** 3423 * Constructs a new Builder with the defaults: 3424 * 3425 * @param context 3426 * A {@link Context} that will be used by the Builder to construct the 3427 * RemoteViews. The Context will not be held past the lifetime of this Builder 3428 * object. 3429 * @param channelId 3430 * The constructed Notification will be posted on this 3431 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 3432 * created using {@link NotificationManager#createNotificationChannel}. 3433 */ Builder(Context context, String channelId)3434 public Builder(Context context, String channelId) { 3435 this(context, (Notification) null); 3436 mN.mChannelId = channelId; 3437 } 3438 3439 /** 3440 * @deprecated use {@link #Builder(Context, String)} 3441 * instead. All posted Notifications must specify a NotificationChannel Id. 3442 */ 3443 @Deprecated Builder(Context context)3444 public Builder(Context context) { 3445 this(context, (Notification) null); 3446 } 3447 3448 /** 3449 * @hide 3450 */ Builder(Context context, Notification toAdopt)3451 public Builder(Context context, Notification toAdopt) { 3452 mContext = context; 3453 Resources res = mContext.getResources(); 3454 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 3455 3456 if (res.getBoolean(R.bool.config_enableNightMode)) { 3457 Configuration currentConfig = res.getConfiguration(); 3458 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 3459 == Configuration.UI_MODE_NIGHT_YES; 3460 } 3461 3462 if (toAdopt == null) { 3463 mN = new Notification(); 3464 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3465 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 3466 } 3467 mN.priority = PRIORITY_DEFAULT; 3468 mN.visibility = VISIBILITY_PRIVATE; 3469 } else { 3470 mN = toAdopt; 3471 if (mN.actions != null) { 3472 Collections.addAll(mActions, mN.actions); 3473 } 3474 3475 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 3476 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 3477 mPersonList.addAll(people); 3478 } 3479 3480 if (mN.getSmallIcon() == null && mN.icon != 0) { 3481 setSmallIcon(mN.icon); 3482 } 3483 3484 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 3485 setLargeIcon(mN.largeIcon); 3486 } 3487 3488 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 3489 if (!TextUtils.isEmpty(templateClass)) { 3490 final Class<? extends Style> styleClass 3491 = getNotificationStyleClass(templateClass); 3492 if (styleClass == null) { 3493 Log.d(TAG, "Unknown style class: " + templateClass); 3494 } else { 3495 try { 3496 final Constructor<? extends Style> ctor = 3497 styleClass.getDeclaredConstructor(); 3498 ctor.setAccessible(true); 3499 final Style style = ctor.newInstance(); 3500 style.restoreFromExtras(mN.extras); 3501 3502 if (style != null) { 3503 setStyle(style); 3504 } 3505 } catch (Throwable t) { 3506 Log.e(TAG, "Could not create Style", t); 3507 } 3508 } 3509 } 3510 3511 } 3512 } 3513 getColorUtil()3514 private ContrastColorUtil getColorUtil() { 3515 if (mColorUtil == null) { 3516 mColorUtil = ContrastColorUtil.getInstance(mContext); 3517 } 3518 return mColorUtil; 3519 } 3520 3521 /** 3522 * If this notification is duplicative of a Launcher shortcut, sets the 3523 * {@link ShortcutInfo#getId() id} of the shortcut, in case the Launcher wants to hide 3524 * the shortcut. 3525 * 3526 * This field will be ignored by Launchers that don't support badging, don't show 3527 * notification content, or don't show {@link android.content.pm.ShortcutManager shortcuts}. 3528 * 3529 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 3530 * supersedes 3531 */ 3532 @NonNull setShortcutId(String shortcutId)3533 public Builder setShortcutId(String shortcutId) { 3534 mN.mShortcutId = shortcutId; 3535 return this; 3536 } 3537 3538 /** 3539 * Sets the {@link LocusId} associated with this notification. 3540 * 3541 * <p>This method should be called when the {@link LocusId} is used in other places (such 3542 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the Android system can 3543 * correlate them. 3544 */ 3545 @NonNull setLocusId(@ullable LocusId locusId)3546 public Builder setLocusId(@Nullable LocusId locusId) { 3547 mN.mLocusId = locusId; 3548 return this; 3549 } 3550 3551 /** 3552 * Sets which icon to display as a badge for this notification. 3553 * 3554 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 3555 * {@link #BADGE_ICON_LARGE}. 3556 * 3557 * Note: This value might be ignored, for launchers that don't support badge icons. 3558 */ 3559 @NonNull setBadgeIconType(int icon)3560 public Builder setBadgeIconType(int icon) { 3561 mN.mBadgeIcon = icon; 3562 return this; 3563 } 3564 3565 /** 3566 * Sets the group alert behavior for this notification. Use this method to mute this 3567 * notification if alerts for this notification's group should be handled by a different 3568 * notification. This is only applicable for notifications that belong to a 3569 * {@link #setGroup(String) group}. This must be called on all notifications you want to 3570 * mute. For example, if you want only the summary of your group to make noise, all 3571 * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. 3572 * 3573 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 3574 */ 3575 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3576 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 3577 mN.mGroupAlertBehavior = groupAlertBehavior; 3578 return this; 3579 } 3580 3581 /** 3582 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 3583 * window over the existing foreground activity. 3584 * 3585 * <p>This data will be ignored unless the notification is posted to a channel that 3586 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 3587 * 3588 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 3589 * collapsed state outside of the notification shade on unlocked devices. When a user 3590 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 3591 */ 3592 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)3593 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 3594 mN.mBubbleMetadata = data; 3595 return this; 3596 } 3597 3598 /** @removed */ 3599 @Deprecated setChannel(String channelId)3600 public Builder setChannel(String channelId) { 3601 mN.mChannelId = channelId; 3602 return this; 3603 } 3604 3605 /** 3606 * Specifies the channel the notification should be delivered on. 3607 */ 3608 @NonNull setChannelId(String channelId)3609 public Builder setChannelId(String channelId) { 3610 mN.mChannelId = channelId; 3611 return this; 3612 } 3613 3614 /** @removed */ 3615 @Deprecated setTimeout(long durationMs)3616 public Builder setTimeout(long durationMs) { 3617 mN.mTimeout = durationMs; 3618 return this; 3619 } 3620 3621 /** 3622 * Specifies a duration in milliseconds after which this notification should be canceled, 3623 * if it is not already canceled. 3624 */ 3625 @NonNull setTimeoutAfter(long durationMs)3626 public Builder setTimeoutAfter(long durationMs) { 3627 mN.mTimeout = durationMs; 3628 return this; 3629 } 3630 3631 /** 3632 * Add a timestamp pertaining to the notification (usually the time the event occurred). 3633 * 3634 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 3635 * shown anymore by default and must be opted into by using 3636 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 3637 * 3638 * @see Notification#when 3639 */ 3640 @NonNull setWhen(long when)3641 public Builder setWhen(long when) { 3642 mN.when = when; 3643 return this; 3644 } 3645 3646 /** 3647 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 3648 * in the content view. 3649 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 3650 * {@code false}. For earlier apps, the default is {@code true}. 3651 */ 3652 @NonNull setShowWhen(boolean show)3653 public Builder setShowWhen(boolean show) { 3654 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 3655 return this; 3656 } 3657 3658 /** 3659 * Show the {@link Notification#when} field as a stopwatch. 3660 * 3661 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 3662 * automatically updating display of the minutes and seconds since <code>when</code>. 3663 * 3664 * Useful when showing an elapsed time (like an ongoing phone call). 3665 * 3666 * The counter can also be set to count down to <code>when</code> when using 3667 * {@link #setChronometerCountDown(boolean)}. 3668 * 3669 * @see android.widget.Chronometer 3670 * @see Notification#when 3671 * @see #setChronometerCountDown(boolean) 3672 */ 3673 @NonNull setUsesChronometer(boolean b)3674 public Builder setUsesChronometer(boolean b) { 3675 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 3676 return this; 3677 } 3678 3679 /** 3680 * Sets the Chronometer to count down instead of counting up. 3681 * 3682 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 3683 * If it isn't set the chronometer will count up. 3684 * 3685 * @see #setUsesChronometer(boolean) 3686 */ 3687 @NonNull setChronometerCountDown(boolean countDown)3688 public Builder setChronometerCountDown(boolean countDown) { 3689 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 3690 return this; 3691 } 3692 3693 /** 3694 * Set the small icon resource, which will be used to represent the notification in the 3695 * status bar. 3696 * 3697 3698 * The platform template for the expanded view will draw this icon in the left, unless a 3699 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 3700 * icon will be moved to the right-hand side. 3701 * 3702 3703 * @param icon 3704 * A resource ID in the application's package of the drawable to use. 3705 * @see Notification#icon 3706 */ 3707 @NonNull setSmallIcon(@rawableRes int icon)3708 public Builder setSmallIcon(@DrawableRes int icon) { 3709 return setSmallIcon(icon != 0 3710 ? Icon.createWithResource(mContext, icon) 3711 : null); 3712 } 3713 3714 /** 3715 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 3716 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 3717 * LevelListDrawable}. 3718 * 3719 * @param icon A resource ID in the application's package of the drawable to use. 3720 * @param level The level to use for the icon. 3721 * 3722 * @see Notification#icon 3723 * @see Notification#iconLevel 3724 */ 3725 @NonNull setSmallIcon(@rawableRes int icon, int level)3726 public Builder setSmallIcon(@DrawableRes int icon, int level) { 3727 mN.iconLevel = level; 3728 return setSmallIcon(icon); 3729 } 3730 3731 /** 3732 * Set the small icon, which will be used to represent the notification in the 3733 * status bar and content view (unless overridden there by a 3734 * {@link #setLargeIcon(Bitmap) large icon}). 3735 * 3736 * @param icon An Icon object to use. 3737 * @see Notification#icon 3738 */ 3739 @NonNull setSmallIcon(Icon icon)3740 public Builder setSmallIcon(Icon icon) { 3741 mN.setSmallIcon(icon); 3742 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 3743 mN.icon = icon.getResId(); 3744 } 3745 return this; 3746 } 3747 3748 /** 3749 * Set the first line of text in the platform notification template. 3750 */ 3751 @NonNull setContentTitle(CharSequence title)3752 public Builder setContentTitle(CharSequence title) { 3753 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 3754 return this; 3755 } 3756 3757 /** 3758 * Set the second line of text in the platform notification template. 3759 */ 3760 @NonNull setContentText(CharSequence text)3761 public Builder setContentText(CharSequence text) { 3762 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 3763 return this; 3764 } 3765 3766 /** 3767 * This provides some additional information that is displayed in the notification. No 3768 * guarantees are given where exactly it is displayed. 3769 * 3770 * <p>This information should only be provided if it provides an essential 3771 * benefit to the understanding of the notification. The more text you provide the 3772 * less readable it becomes. For example, an email client should only provide the account 3773 * name here if more than one email account has been added.</p> 3774 * 3775 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 3776 * notification header area. 3777 * 3778 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 3779 * this will be shown in the third line of text in the platform notification template. 3780 * You should not be using {@link #setProgress(int, int, boolean)} at the 3781 * same time on those versions; they occupy the same place. 3782 * </p> 3783 */ 3784 @NonNull setSubText(CharSequence text)3785 public Builder setSubText(CharSequence text) { 3786 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 3787 return this; 3788 } 3789 3790 /** 3791 * Provides text that will appear as a link to your application's settings. 3792 * 3793 * <p>This text does not appear within notification {@link Style templates} but may 3794 * appear when the user uses an affordance to learn more about the notification. 3795 * Additionally, this text will not appear unless you provide a valid link target by 3796 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 3797 * 3798 * <p>This text is meant to be concise description about what the user can customize 3799 * when they click on this link. The recommended maximum length is 40 characters. 3800 * @param text 3801 * @return 3802 */ 3803 @NonNull setSettingsText(CharSequence text)3804 public Builder setSettingsText(CharSequence text) { 3805 mN.mSettingsText = safeCharSequence(text); 3806 return this; 3807 } 3808 3809 /** 3810 * Set the remote input history. 3811 * 3812 * This should be set to the most recent inputs that have been sent 3813 * through a {@link RemoteInput} of this Notification and cleared once the it is no 3814 * longer relevant (e.g. for chat notifications once the other party has responded). 3815 * 3816 * The most recent input must be stored at the 0 index, the second most recent at the 3817 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 3818 * and how much of each individual input is shown. 3819 * 3820 * <p>Note: The reply text will only be shown on notifications that have least one action 3821 * with a {@code RemoteInput}.</p> 3822 */ 3823 @NonNull setRemoteInputHistory(CharSequence[] text)3824 public Builder setRemoteInputHistory(CharSequence[] text) { 3825 if (text == null) { 3826 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 3827 } else { 3828 final int N = Math.min(MAX_REPLY_HISTORY, text.length); 3829 CharSequence[] safe = new CharSequence[N]; 3830 for (int i = 0; i < N; i++) { 3831 safe[i] = safeCharSequence(text[i]); 3832 } 3833 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 3834 } 3835 return this; 3836 } 3837 3838 /** 3839 * Sets whether remote history entries view should have a spinner. 3840 * @hide 3841 */ 3842 @NonNull setShowRemoteInputSpinner(boolean showSpinner)3843 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 3844 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 3845 return this; 3846 } 3847 3848 /** 3849 * Sets whether smart reply buttons should be hidden. 3850 * @hide 3851 */ 3852 @NonNull setHideSmartReplies(boolean hideSmartReplies)3853 public Builder setHideSmartReplies(boolean hideSmartReplies) { 3854 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 3855 return this; 3856 } 3857 3858 /** 3859 * Sets the number of items this notification represents. May be displayed as a badge count 3860 * for Launchers that support badging. 3861 */ 3862 @NonNull setNumber(int number)3863 public Builder setNumber(int number) { 3864 mN.number = number; 3865 return this; 3866 } 3867 3868 /** 3869 * A small piece of additional information pertaining to this notification. 3870 * 3871 * The platform template will draw this on the last line of the notification, at the far 3872 * right (to the right of a smallIcon if it has been placed there). 3873 * 3874 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 3875 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 3876 * field will still show up, but the subtext will take precedence. 3877 */ 3878 @Deprecated setContentInfo(CharSequence info)3879 public Builder setContentInfo(CharSequence info) { 3880 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 3881 return this; 3882 } 3883 3884 /** 3885 * Set the progress this notification represents. 3886 * 3887 * The platform template will represent this using a {@link ProgressBar}. 3888 */ 3889 @NonNull setProgress(int max, int progress, boolean indeterminate)3890 public Builder setProgress(int max, int progress, boolean indeterminate) { 3891 mN.extras.putInt(EXTRA_PROGRESS, progress); 3892 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 3893 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 3894 return this; 3895 } 3896 3897 /** 3898 * Supply a custom RemoteViews to use instead of the platform template. 3899 * 3900 * Use {@link #setCustomContentView(RemoteViews)} instead. 3901 */ 3902 @Deprecated setContent(RemoteViews views)3903 public Builder setContent(RemoteViews views) { 3904 return setCustomContentView(views); 3905 } 3906 3907 /** 3908 * Supply custom RemoteViews to use instead of the platform template. 3909 * 3910 * This will override the layout that would otherwise be constructed by this Builder 3911 * object. 3912 */ 3913 @NonNull setCustomContentView(RemoteViews contentView)3914 public Builder setCustomContentView(RemoteViews contentView) { 3915 mN.contentView = contentView; 3916 return this; 3917 } 3918 3919 /** 3920 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 3921 * 3922 * This will override the expanded layout that would otherwise be constructed by this 3923 * Builder object. 3924 */ 3925 @NonNull setCustomBigContentView(RemoteViews contentView)3926 public Builder setCustomBigContentView(RemoteViews contentView) { 3927 mN.bigContentView = contentView; 3928 return this; 3929 } 3930 3931 /** 3932 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 3933 * 3934 * This will override the heads-up layout that would otherwise be constructed by this 3935 * Builder object. 3936 */ 3937 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)3938 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 3939 mN.headsUpContentView = contentView; 3940 return this; 3941 } 3942 3943 /** 3944 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 3945 * 3946 * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 3947 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 3948 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 3949 * to assign PendingIntents to individual views in that custom layout (i.e., to create 3950 * clickable buttons inside the notification view). 3951 * 3952 * @see Notification#contentIntent Notification.contentIntent 3953 */ 3954 @NonNull setContentIntent(PendingIntent intent)3955 public Builder setContentIntent(PendingIntent intent) { 3956 mN.contentIntent = intent; 3957 return this; 3958 } 3959 3960 /** 3961 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 3962 * 3963 * @see Notification#deleteIntent 3964 */ 3965 @NonNull setDeleteIntent(PendingIntent intent)3966 public Builder setDeleteIntent(PendingIntent intent) { 3967 mN.deleteIntent = intent; 3968 return this; 3969 } 3970 3971 /** 3972 * An intent to launch instead of posting the notification to the status bar. 3973 * Only for use with extremely high-priority notifications demanding the user's 3974 * <strong>immediate</strong> attention, such as an incoming phone call or 3975 * alarm clock that the user has explicitly set to a particular time. 3976 * If this facility is used for something else, please give the user an option 3977 * to turn it off and use a normal notification, as this can be extremely 3978 * disruptive. 3979 * 3980 * <p> 3981 * The system UI may choose to display a heads-up notification, instead of 3982 * launching this intent, while the user is using the device. 3983 * </p> 3984 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 3985 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 3986 * use full screen intents.</p> 3987 * 3988 * @param intent The pending intent to launch. 3989 * @param highPriority Passing true will cause this notification to be sent 3990 * even if other notifications are suppressed. 3991 * 3992 * @see Notification#fullScreenIntent 3993 */ 3994 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)3995 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 3996 mN.fullScreenIntent = intent; 3997 setFlag(FLAG_HIGH_PRIORITY, highPriority); 3998 return this; 3999 } 4000 4001 /** 4002 * Set the "ticker" text which is sent to accessibility services. 4003 * 4004 * @see Notification#tickerText 4005 */ 4006 @NonNull setTicker(CharSequence tickerText)4007 public Builder setTicker(CharSequence tickerText) { 4008 mN.tickerText = safeCharSequence(tickerText); 4009 return this; 4010 } 4011 4012 /** 4013 * Obsolete version of {@link #setTicker(CharSequence)}. 4014 * 4015 */ 4016 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)4017 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 4018 setTicker(tickerText); 4019 // views is ignored 4020 return this; 4021 } 4022 4023 /** 4024 * Add a large icon to the notification content view. 4025 * 4026 * In the platform template, this image will be shown on the left of the notification view 4027 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 4028 * badge atop the large icon). 4029 */ 4030 @NonNull setLargeIcon(Bitmap b)4031 public Builder setLargeIcon(Bitmap b) { 4032 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4033 } 4034 4035 /** 4036 * Add a large icon to the notification content view. 4037 * 4038 * In the platform template, this image will be shown on the left of the notification view 4039 * in place of the {@link #setSmallIcon(Icon) small icon} (which will be placed in a small 4040 * badge atop the large icon). 4041 */ 4042 @NonNull setLargeIcon(Icon icon)4043 public Builder setLargeIcon(Icon icon) { 4044 mN.mLargeIcon = icon; 4045 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 4046 return this; 4047 } 4048 4049 /** 4050 * Set the sound to play. 4051 * 4052 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 4053 * for notifications. 4054 * 4055 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4056 */ 4057 @Deprecated setSound(Uri sound)4058 public Builder setSound(Uri sound) { 4059 mN.sound = sound; 4060 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 4061 return this; 4062 } 4063 4064 /** 4065 * Set the sound to play, along with a specific stream on which to play it. 4066 * 4067 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 4068 * 4069 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 4070 */ 4071 @Deprecated setSound(Uri sound, int streamType)4072 public Builder setSound(Uri sound, int streamType) { 4073 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 4074 mN.sound = sound; 4075 mN.audioStreamType = streamType; 4076 return this; 4077 } 4078 4079 /** 4080 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 4081 * use during playback. 4082 * 4083 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4084 * @see Notification#sound 4085 */ 4086 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)4087 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 4088 mN.sound = sound; 4089 mN.audioAttributes = audioAttributes; 4090 return this; 4091 } 4092 4093 /** 4094 * Set the vibration pattern to use. 4095 * 4096 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 4097 * <code>pattern</code> parameter. 4098 * 4099 * <p> 4100 * A notification that vibrates is more likely to be presented as a heads-up notification. 4101 * </p> 4102 * 4103 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 4104 * @see Notification#vibrate 4105 */ 4106 @Deprecated setVibrate(long[] pattern)4107 public Builder setVibrate(long[] pattern) { 4108 mN.vibrate = pattern; 4109 return this; 4110 } 4111 4112 /** 4113 * Set the desired color for the indicator LED on the device, as well as the 4114 * blink duty cycle (specified in milliseconds). 4115 * 4116 4117 * Not all devices will honor all (or even any) of these values. 4118 * 4119 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 4120 * @see Notification#ledARGB 4121 * @see Notification#ledOnMS 4122 * @see Notification#ledOffMS 4123 */ 4124 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)4125 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 4126 mN.ledARGB = argb; 4127 mN.ledOnMS = onMs; 4128 mN.ledOffMS = offMs; 4129 if (onMs != 0 || offMs != 0) { 4130 mN.flags |= FLAG_SHOW_LIGHTS; 4131 } 4132 return this; 4133 } 4134 4135 /** 4136 * Set whether this is an "ongoing" notification. 4137 * 4138 4139 * Ongoing notifications cannot be dismissed by the user, so your application or service 4140 * must take care of canceling them. 4141 * 4142 4143 * They are typically used to indicate a background task that the user is actively engaged 4144 * with (e.g., playing music) or is pending in some way and therefore occupying the device 4145 * (e.g., a file download, sync operation, active network connection). 4146 * 4147 4148 * @see Notification#FLAG_ONGOING_EVENT 4149 */ 4150 @NonNull setOngoing(boolean ongoing)4151 public Builder setOngoing(boolean ongoing) { 4152 setFlag(FLAG_ONGOING_EVENT, ongoing); 4153 return this; 4154 } 4155 4156 /** 4157 * Set whether this notification should be colorized. When set, the color set with 4158 * {@link #setColor(int)} will be used as the background color of this notification. 4159 * <p> 4160 * This should only be used for high priority ongoing tasks like navigation, an ongoing 4161 * call, or other similarly high-priority events for the user. 4162 * <p> 4163 * For most styles, the coloring will only be applied if the notification is for a 4164 * foreground service notification. 4165 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 4166 * that have a media session attached there is no such requirement. 4167 * 4168 * @see #setColor(int) 4169 * @see MediaStyle#setMediaSession(MediaSession.Token) 4170 */ 4171 @NonNull setColorized(boolean colorize)4172 public Builder setColorized(boolean colorize) { 4173 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 4174 return this; 4175 } 4176 4177 /** 4178 * Set this flag if you would only like the sound, vibrate 4179 * and ticker to be played if the notification is not already showing. 4180 * 4181 * @see Notification#FLAG_ONLY_ALERT_ONCE 4182 */ 4183 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)4184 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 4185 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 4186 return this; 4187 } 4188 4189 /** 4190 * Make this notification automatically dismissed when the user touches it. 4191 * 4192 * @see Notification#FLAG_AUTO_CANCEL 4193 */ 4194 @NonNull setAutoCancel(boolean autoCancel)4195 public Builder setAutoCancel(boolean autoCancel) { 4196 setFlag(FLAG_AUTO_CANCEL, autoCancel); 4197 return this; 4198 } 4199 4200 /** 4201 * Set whether or not this notification should not bridge to other devices. 4202 * 4203 * <p>Some notifications can be bridged to other devices for remote display. 4204 * This hint can be set to recommend this notification not be bridged. 4205 */ 4206 @NonNull setLocalOnly(boolean localOnly)4207 public Builder setLocalOnly(boolean localOnly) { 4208 setFlag(FLAG_LOCAL_ONLY, localOnly); 4209 return this; 4210 } 4211 4212 /** 4213 * Set which notification properties will be inherited from system defaults. 4214 * <p> 4215 * The value should be one or more of the following fields combined with 4216 * bitwise-or: 4217 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 4218 * <p> 4219 * For all default values, use {@link #DEFAULT_ALL}. 4220 * 4221 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 4222 * {@link NotificationChannel#enableLights(boolean)} and 4223 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4224 */ 4225 @Deprecated setDefaults(int defaults)4226 public Builder setDefaults(int defaults) { 4227 mN.defaults = defaults; 4228 return this; 4229 } 4230 4231 /** 4232 * Set the priority of this notification. 4233 * 4234 * @see Notification#priority 4235 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 4236 */ 4237 @Deprecated setPriority(@riority int pri)4238 public Builder setPriority(@Priority int pri) { 4239 mN.priority = pri; 4240 return this; 4241 } 4242 4243 /** 4244 * Set the notification category. 4245 * 4246 * @see Notification#category 4247 */ 4248 @NonNull setCategory(String category)4249 public Builder setCategory(String category) { 4250 mN.category = category; 4251 return this; 4252 } 4253 4254 /** 4255 * Add a person that is relevant to this notification. 4256 * 4257 * <P> 4258 * Depending on user preferences, this annotation may allow the notification to pass 4259 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4260 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4261 * appear more prominently in the user interface. 4262 * </P> 4263 * 4264 * <P> 4265 * The person should be specified by the {@code String} representation of a 4266 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 4267 * </P> 4268 * 4269 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 4270 * URIs. The path part of these URIs must exist in the contacts database, in the 4271 * appropriate column, or the reference will be discarded as invalid. Telephone schema 4272 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 4273 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 4274 * identify a person without an entry in the contacts database. 4275 * </P> 4276 * 4277 * @param uri A URI for the person. 4278 * @see Notification#EXTRA_PEOPLE 4279 * @deprecated use {@link #addPerson(Person)} 4280 */ addPerson(String uri)4281 public Builder addPerson(String uri) { 4282 addPerson(new Person.Builder().setUri(uri).build()); 4283 return this; 4284 } 4285 4286 /** 4287 * Add a person that is relevant to this notification. 4288 * 4289 * <P> 4290 * Depending on user preferences, this annotation may allow the notification to pass 4291 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4292 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4293 * appear more prominently in the user interface. 4294 * </P> 4295 * 4296 * <P> 4297 * A person should usually contain a uri in order to benefit from the ranking boost. 4298 * However, even if no uri is provided, it's beneficial to provide other people in the 4299 * notification, such that listeners and voice only devices can announce and handle them 4300 * properly. 4301 * </P> 4302 * 4303 * @param person the person to add. 4304 * @see Notification#EXTRA_PEOPLE_LIST 4305 */ 4306 @NonNull addPerson(Person person)4307 public Builder addPerson(Person person) { 4308 mPersonList.add(person); 4309 return this; 4310 } 4311 4312 /** 4313 * Set this notification to be part of a group of notifications sharing the same key. 4314 * Grouped notifications may display in a cluster or stack on devices which 4315 * support such rendering. 4316 * 4317 * <p>To make this notification the summary for its group, also call 4318 * {@link #setGroupSummary}. A sort order can be specified for group members by using 4319 * {@link #setSortKey}. 4320 * @param groupKey The group key of the group. 4321 * @return this object for method chaining 4322 */ 4323 @NonNull setGroup(String groupKey)4324 public Builder setGroup(String groupKey) { 4325 mN.mGroupKey = groupKey; 4326 return this; 4327 } 4328 4329 /** 4330 * Set this notification to be the group summary for a group of notifications. 4331 * Grouped notifications may display in a cluster or stack on devices which 4332 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 4333 * The group summary may be suppressed if too few notifications are included in the group. 4334 * @param isGroupSummary Whether this notification should be a group summary. 4335 * @return this object for method chaining 4336 */ 4337 @NonNull setGroupSummary(boolean isGroupSummary)4338 public Builder setGroupSummary(boolean isGroupSummary) { 4339 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 4340 return this; 4341 } 4342 4343 /** 4344 * Set a sort key that orders this notification among other notifications from the 4345 * same package. This can be useful if an external sort was already applied and an app 4346 * would like to preserve this. Notifications will be sorted lexicographically using this 4347 * value, although providing different priorities in addition to providing sort key may 4348 * cause this value to be ignored. 4349 * 4350 * <p>This sort key can also be used to order members of a notification group. See 4351 * {@link #setGroup}. 4352 * 4353 * @see String#compareTo(String) 4354 */ 4355 @NonNull setSortKey(String sortKey)4356 public Builder setSortKey(String sortKey) { 4357 mN.mSortKey = sortKey; 4358 return this; 4359 } 4360 4361 /** 4362 * Merge additional metadata into this notification. 4363 * 4364 * <p>Values within the Bundle will replace existing extras values in this Builder. 4365 * 4366 * @see Notification#extras 4367 */ 4368 @NonNull addExtras(Bundle extras)4369 public Builder addExtras(Bundle extras) { 4370 if (extras != null) { 4371 mUserExtras.putAll(extras); 4372 } 4373 return this; 4374 } 4375 4376 /** 4377 * Set metadata for this notification. 4378 * 4379 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 4380 * current contents are copied into the Notification each time {@link #build()} is 4381 * called. 4382 * 4383 * <p>Replaces any existing extras values with those from the provided Bundle. 4384 * Use {@link #addExtras} to merge in metadata instead. 4385 * 4386 * @see Notification#extras 4387 */ 4388 @NonNull setExtras(Bundle extras)4389 public Builder setExtras(Bundle extras) { 4390 if (extras != null) { 4391 mUserExtras = extras; 4392 } 4393 return this; 4394 } 4395 4396 /** 4397 * Get the current metadata Bundle used by this notification Builder. 4398 * 4399 * <p>The returned Bundle is shared with this Builder. 4400 * 4401 * <p>The current contents of this Bundle are copied into the Notification each time 4402 * {@link #build()} is called. 4403 * 4404 * @see Notification#extras 4405 */ getExtras()4406 public Bundle getExtras() { 4407 return mUserExtras; 4408 } 4409 getAllExtras()4410 private Bundle getAllExtras() { 4411 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 4412 saveExtras.putAll(mN.extras); 4413 return saveExtras; 4414 } 4415 4416 /** 4417 * Add an action to this notification. Actions are typically displayed by 4418 * the system as a button adjacent to the notification content. 4419 * <p> 4420 * Every action must have an icon (32dp square and matching the 4421 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4422 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4423 * <p> 4424 * A notification in its expanded form can display up to 3 actions, from left to right in 4425 * the order they were added. Actions will not be displayed when the notification is 4426 * collapsed, however, so be sure that any essential functions may be accessed by the user 4427 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4428 * 4429 * @param icon Resource ID of a drawable that represents the action. 4430 * @param title Text describing the action. 4431 * @param intent PendingIntent to be fired when the action is invoked. 4432 * 4433 * @deprecated Use {@link #addAction(Action)} instead. 4434 */ 4435 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)4436 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 4437 mActions.add(new Action(icon, safeCharSequence(title), intent)); 4438 return this; 4439 } 4440 4441 /** 4442 * Add an action to this notification. Actions are typically displayed by 4443 * the system as a button adjacent to the notification content. 4444 * <p> 4445 * Every action must have an icon (32dp square and matching the 4446 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4447 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4448 * <p> 4449 * A notification in its expanded form can display up to 3 actions, from left to right in 4450 * the order they were added. Actions will not be displayed when the notification is 4451 * collapsed, however, so be sure that any essential functions may be accessed by the user 4452 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4453 * 4454 * @param action The action to add. 4455 */ 4456 @NonNull addAction(Action action)4457 public Builder addAction(Action action) { 4458 if (action != null) { 4459 mActions.add(action); 4460 } 4461 return this; 4462 } 4463 4464 /** 4465 * Alter the complete list of actions attached to this notification. 4466 * @see #addAction(Action). 4467 * 4468 * @param actions 4469 * @return 4470 */ 4471 @NonNull setActions(Action... actions)4472 public Builder setActions(Action... actions) { 4473 mActions.clear(); 4474 for (int i = 0; i < actions.length; i++) { 4475 if (actions[i] != null) { 4476 mActions.add(actions[i]); 4477 } 4478 } 4479 return this; 4480 } 4481 4482 /** 4483 * Add a rich notification style to be applied at build time. 4484 * 4485 * @param style Object responsible for modifying the notification style. 4486 */ 4487 @NonNull setStyle(Style style)4488 public Builder setStyle(Style style) { 4489 if (mStyle != style) { 4490 mStyle = style; 4491 if (mStyle != null) { 4492 mStyle.setBuilder(this); 4493 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 4494 } else { 4495 mN.extras.remove(EXTRA_TEMPLATE); 4496 } 4497 } 4498 return this; 4499 } 4500 4501 /** 4502 * Returns the style set by {@link #setStyle(Style)}. 4503 */ getStyle()4504 public Style getStyle() { 4505 return mStyle; 4506 } 4507 4508 /** 4509 * Specify the value of {@link #visibility}. 4510 * 4511 * @return The same Builder. 4512 */ 4513 @NonNull setVisibility(@isibility int visibility)4514 public Builder setVisibility(@Visibility int visibility) { 4515 mN.visibility = visibility; 4516 return this; 4517 } 4518 4519 /** 4520 * Supply a replacement Notification whose contents should be shown in insecure contexts 4521 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 4522 * @param n A replacement notification, presumably with some or all info redacted. 4523 * @return The same Builder. 4524 */ 4525 @NonNull setPublicVersion(Notification n)4526 public Builder setPublicVersion(Notification n) { 4527 if (n != null) { 4528 mN.publicVersion = new Notification(); 4529 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 4530 } else { 4531 mN.publicVersion = null; 4532 } 4533 return this; 4534 } 4535 4536 /** 4537 * Apply an extender to this notification builder. Extenders may be used to add 4538 * metadata or change options on this builder. 4539 */ 4540 @NonNull extend(Extender extender)4541 public Builder extend(Extender extender) { 4542 extender.extend(this); 4543 return this; 4544 } 4545 4546 /** 4547 * Set the value for a notification flag 4548 * 4549 * @param mask Bit mask of the flag 4550 * @param value Status (on/off) of the flag 4551 * 4552 * @return The same Builder. 4553 */ 4554 @NonNull setFlag(@otificationFlags int mask, boolean value)4555 public Builder setFlag(@NotificationFlags int mask, boolean value) { 4556 if (value) { 4557 mN.flags |= mask; 4558 } else { 4559 mN.flags &= ~mask; 4560 } 4561 return this; 4562 } 4563 4564 /** 4565 * Sets {@link Notification#color}. 4566 * 4567 * @param argb The accent color to use 4568 * 4569 * @return The same Builder. 4570 */ 4571 @NonNull setColor(@olorInt int argb)4572 public Builder setColor(@ColorInt int argb) { 4573 mN.color = argb; 4574 sanitizeColor(); 4575 return this; 4576 } 4577 getProfileBadgeDrawable()4578 private Drawable getProfileBadgeDrawable() { 4579 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 4580 // This user can never be a badged profile, 4581 // and also includes USER_ALL system notifications. 4582 return null; 4583 } 4584 // Note: This assumes that the current user can read the profile badge of the 4585 // originating user. 4586 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 4587 new UserHandle(mContext.getUserId()), 0); 4588 } 4589 getProfileBadge()4590 private Bitmap getProfileBadge() { 4591 Drawable badge = getProfileBadgeDrawable(); 4592 if (badge == null) { 4593 return null; 4594 } 4595 final int size = mContext.getResources().getDimensionPixelSize( 4596 R.dimen.notification_badge_size); 4597 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 4598 Canvas canvas = new Canvas(bitmap); 4599 badge.setBounds(0, 0, size, size); 4600 badge.draw(canvas); 4601 return bitmap; 4602 } 4603 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)4604 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 4605 Bitmap profileBadge = getProfileBadge(); 4606 4607 if (profileBadge != null) { 4608 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 4609 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 4610 if (isColorized(p)) { 4611 contentView.setDrawableTint(R.id.profile_badge, false, 4612 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 4613 } 4614 } 4615 } 4616 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)4617 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 4618 contentView.setDrawableTint( 4619 R.id.alerted_icon, 4620 false /* targetBackground */, 4621 getNeutralColor(p), 4622 PorterDuff.Mode.SRC_ATOP); 4623 } 4624 4625 /** 4626 * @hide 4627 */ usesStandardHeader()4628 public boolean usesStandardHeader() { 4629 if (mN.mUsesStandardHeader) { 4630 return true; 4631 } 4632 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 4633 if (mN.contentView == null && mN.bigContentView == null) { 4634 return true; 4635 } 4636 } 4637 boolean contentViewUsesHeader = mN.contentView == null 4638 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 4639 boolean bigContentViewUsesHeader = mN.bigContentView == null 4640 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 4641 return contentViewUsesHeader && bigContentViewUsesHeader; 4642 } 4643 resetStandardTemplate(RemoteViews contentView)4644 private void resetStandardTemplate(RemoteViews contentView) { 4645 resetNotificationHeader(contentView); 4646 contentView.setViewVisibility(R.id.right_icon, View.GONE); 4647 contentView.setViewVisibility(R.id.title, View.GONE); 4648 contentView.setTextViewText(R.id.title, null); 4649 contentView.setViewVisibility(R.id.text, View.GONE); 4650 contentView.setTextViewText(R.id.text, null); 4651 contentView.setViewVisibility(R.id.text_line_1, View.GONE); 4652 contentView.setTextViewText(R.id.text_line_1, null); 4653 } 4654 4655 /** 4656 * Resets the notification header to its original state 4657 */ resetNotificationHeader(RemoteViews contentView)4658 private void resetNotificationHeader(RemoteViews contentView) { 4659 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 4660 // re-using the drawable when the notification is updated. 4661 contentView.setBoolean(R.id.notification_header, "setExpanded", false); 4662 contentView.setTextViewText(R.id.app_name_text, null); 4663 contentView.setViewVisibility(R.id.chronometer, View.GONE); 4664 contentView.setViewVisibility(R.id.header_text, View.GONE); 4665 contentView.setTextViewText(R.id.header_text, null); 4666 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 4667 contentView.setTextViewText(R.id.header_text_secondary, null); 4668 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 4669 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 4670 contentView.setViewVisibility(R.id.time_divider, View.GONE); 4671 contentView.setViewVisibility(R.id.time, View.GONE); 4672 contentView.setImageViewIcon(R.id.profile_badge, null); 4673 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 4674 contentView.setViewVisibility(R.id.alerted_icon, View.GONE); 4675 mN.mUsesStandardHeader = false; 4676 } 4677 applyStandardTemplate(int resId, TemplateBindResult result)4678 private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) { 4679 return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this), 4680 result); 4681 } 4682 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)4683 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 4684 TemplateBindResult result) { 4685 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 4686 4687 resetStandardTemplate(contentView); 4688 4689 final Bundle ex = mN.extras; 4690 updateBackgroundColor(contentView, p); 4691 bindNotificationHeader(contentView, p); 4692 bindLargeIconAndReply(contentView, p, result); 4693 boolean showProgress = handleProgressBar(contentView, ex, p); 4694 if (p.title != null) { 4695 contentView.setViewVisibility(R.id.title, View.VISIBLE); 4696 contentView.setTextViewText(R.id.title, processTextSpans(p.title)); 4697 setTextViewColorPrimary(contentView, R.id.title, p); 4698 contentView.setViewLayoutWidth(R.id.title, showProgress 4699 ? ViewGroup.LayoutParams.WRAP_CONTENT 4700 : ViewGroup.LayoutParams.MATCH_PARENT); 4701 } 4702 if (p.text != null) { 4703 int textId = showProgress ? com.android.internal.R.id.text_line_1 4704 : com.android.internal.R.id.text; 4705 contentView.setTextViewText(textId, processTextSpans(p.text)); 4706 setTextViewColorSecondary(contentView, textId, p); 4707 contentView.setViewVisibility(textId, View.VISIBLE); 4708 } 4709 4710 setContentMinHeight(contentView, showProgress || mN.hasLargeIcon()); 4711 4712 return contentView; 4713 } 4714 processTextSpans(CharSequence text)4715 private CharSequence processTextSpans(CharSequence text) { 4716 if (hasForegroundColor() || mInNightMode) { 4717 return ContrastColorUtil.clearColorSpans(text); 4718 } 4719 return text; 4720 } 4721 setTextViewColorPrimary(RemoteViews contentView, int id, StandardTemplateParams p)4722 private void setTextViewColorPrimary(RemoteViews contentView, int id, 4723 StandardTemplateParams p) { 4724 ensureColors(p); 4725 contentView.setTextColor(id, mPrimaryTextColor); 4726 } 4727 hasForegroundColor()4728 private boolean hasForegroundColor() { 4729 return mForegroundColor != COLOR_INVALID; 4730 } 4731 4732 /** 4733 * Return the primary text color using the existing template params 4734 * @hide 4735 */ 4736 @VisibleForTesting getPrimaryTextColor()4737 public int getPrimaryTextColor() { 4738 return getPrimaryTextColor(mParams); 4739 } 4740 4741 /** 4742 * @param p the template params to inflate this with 4743 * @return the primary text color 4744 * @hide 4745 */ 4746 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)4747 public int getPrimaryTextColor(StandardTemplateParams p) { 4748 ensureColors(p); 4749 return mPrimaryTextColor; 4750 } 4751 4752 /** 4753 * Return the secondary text color using the existing template params 4754 * @hide 4755 */ 4756 @VisibleForTesting getSecondaryTextColor()4757 public int getSecondaryTextColor() { 4758 return getSecondaryTextColor(mParams); 4759 } 4760 4761 /** 4762 * @param p the template params to inflate this with 4763 * @return the secondary text color 4764 * @hide 4765 */ 4766 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)4767 public int getSecondaryTextColor(StandardTemplateParams p) { 4768 ensureColors(p); 4769 return mSecondaryTextColor; 4770 } 4771 setTextViewColorSecondary(RemoteViews contentView, int id, StandardTemplateParams p)4772 private void setTextViewColorSecondary(RemoteViews contentView, int id, 4773 StandardTemplateParams p) { 4774 ensureColors(p); 4775 contentView.setTextColor(id, mSecondaryTextColor); 4776 } 4777 ensureColors(StandardTemplateParams p)4778 private void ensureColors(StandardTemplateParams p) { 4779 int backgroundColor = getBackgroundColor(p); 4780 if (mPrimaryTextColor == COLOR_INVALID 4781 || mSecondaryTextColor == COLOR_INVALID 4782 || mTextColorsAreForBackground != backgroundColor) { 4783 mTextColorsAreForBackground = backgroundColor; 4784 if (!hasForegroundColor() || !isColorized(p)) { 4785 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext, 4786 backgroundColor, mInNightMode); 4787 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext, 4788 backgroundColor, mInNightMode); 4789 if (backgroundColor != COLOR_DEFAULT && isColorized(p)) { 4790 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 4791 mPrimaryTextColor, backgroundColor, 4.5); 4792 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 4793 mSecondaryTextColor, backgroundColor, 4.5); 4794 } 4795 } else { 4796 double backLum = ContrastColorUtil.calculateLuminance(backgroundColor); 4797 double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor); 4798 double contrast = ContrastColorUtil.calculateContrast(mForegroundColor, 4799 backgroundColor); 4800 // We only respect the given colors if worst case Black or White still has 4801 // contrast 4802 boolean backgroundLight = backLum > textLum 4803 && satisfiesTextContrast(backgroundColor, Color.BLACK) 4804 || backLum <= textLum 4805 && !satisfiesTextContrast(backgroundColor, Color.WHITE); 4806 if (contrast < 4.5f) { 4807 if (backgroundLight) { 4808 mSecondaryTextColor = ContrastColorUtil.findContrastColor( 4809 mForegroundColor, 4810 backgroundColor, 4811 true /* findFG */, 4812 4.5f); 4813 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4814 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT); 4815 } else { 4816 mSecondaryTextColor = 4817 ContrastColorUtil.findContrastColorAgainstDark( 4818 mForegroundColor, 4819 backgroundColor, 4820 true /* findFG */, 4821 4.5f); 4822 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4823 mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK); 4824 } 4825 } else { 4826 mPrimaryTextColor = mForegroundColor; 4827 mSecondaryTextColor = ContrastColorUtil.changeColorLightness( 4828 mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT 4829 : LIGHTNESS_TEXT_DIFFERENCE_DARK); 4830 if (ContrastColorUtil.calculateContrast(mSecondaryTextColor, 4831 backgroundColor) < 4.5f) { 4832 // oh well the secondary is not good enough 4833 if (backgroundLight) { 4834 mSecondaryTextColor = ContrastColorUtil.findContrastColor( 4835 mSecondaryTextColor, 4836 backgroundColor, 4837 true /* findFG */, 4838 4.5f); 4839 } else { 4840 mSecondaryTextColor 4841 = ContrastColorUtil.findContrastColorAgainstDark( 4842 mSecondaryTextColor, 4843 backgroundColor, 4844 true /* findFG */, 4845 4.5f); 4846 } 4847 mPrimaryTextColor = ContrastColorUtil.changeColorLightness( 4848 mSecondaryTextColor, backgroundLight 4849 ? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT 4850 : -LIGHTNESS_TEXT_DIFFERENCE_DARK); 4851 } 4852 } 4853 } 4854 } 4855 } 4856 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)4857 private void updateBackgroundColor(RemoteViews contentView, 4858 StandardTemplateParams p) { 4859 if (isColorized(p)) { 4860 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 4861 getBackgroundColor(p)); 4862 } else { 4863 // Clear it! 4864 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 4865 0); 4866 } 4867 } 4868 4869 /** 4870 * @param remoteView the remote view to update the minheight in 4871 * @param hasMinHeight does it have a mimHeight 4872 * @hide 4873 */ setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight)4874 void setContentMinHeight(RemoteViews remoteView, boolean hasMinHeight) { 4875 int minHeight = 0; 4876 if (hasMinHeight) { 4877 // we need to set the minHeight of the notification 4878 minHeight = mContext.getResources().getDimensionPixelSize( 4879 com.android.internal.R.dimen.notification_min_content_height); 4880 } 4881 remoteView.setInt(R.id.notification_main_column, "setMinimumHeight", minHeight); 4882 } 4883 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)4884 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 4885 StandardTemplateParams p) { 4886 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 4887 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 4888 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 4889 if (p.hasProgress && (max != 0 || ind)) { 4890 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 4891 contentView.setProgressBar( 4892 R.id.progress, max, progress, ind); 4893 contentView.setProgressBackgroundTintList( 4894 R.id.progress, ColorStateList.valueOf(mContext.getColor( 4895 R.color.notification_progress_background_color))); 4896 if (getRawColor(p) != COLOR_DEFAULT) { 4897 int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p); 4898 ColorStateList colorStateList = ColorStateList.valueOf(color); 4899 contentView.setProgressTintList(R.id.progress, colorStateList); 4900 contentView.setProgressIndeterminateTintList(R.id.progress, colorStateList); 4901 } 4902 return true; 4903 } else { 4904 contentView.setViewVisibility(R.id.progress, View.GONE); 4905 return false; 4906 } 4907 } 4908 bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, TemplateBindResult result)4909 private void bindLargeIconAndReply(RemoteViews contentView, StandardTemplateParams p, 4910 TemplateBindResult result) { 4911 boolean largeIconShown = bindLargeIcon(contentView, p); 4912 boolean replyIconShown = bindReplyIcon(contentView, p); 4913 boolean iconContainerVisible = largeIconShown || replyIconShown; 4914 contentView.setViewVisibility(R.id.right_icon_container, 4915 iconContainerVisible ? View.VISIBLE : View.GONE); 4916 int marginEnd = calculateMarginEnd(largeIconShown, replyIconShown); 4917 contentView.setViewLayoutMarginEnd(R.id.line1, marginEnd); 4918 contentView.setViewLayoutMarginEnd(R.id.text, marginEnd); 4919 contentView.setViewLayoutMarginEnd(R.id.progress, marginEnd); 4920 if (result != null) { 4921 result.setIconMarginEnd(marginEnd); 4922 result.setRightIconContainerVisible(iconContainerVisible); 4923 } 4924 } 4925 calculateMarginEnd(boolean largeIconShown, boolean replyIconShown)4926 private int calculateMarginEnd(boolean largeIconShown, boolean replyIconShown) { 4927 int marginEnd = 0; 4928 int contentMargin = mContext.getResources().getDimensionPixelSize( 4929 R.dimen.notification_content_margin_end); 4930 int iconSize = mContext.getResources().getDimensionPixelSize( 4931 R.dimen.notification_right_icon_size); 4932 if (replyIconShown) { 4933 // The size of the reply icon 4934 marginEnd += iconSize; 4935 4936 int replyInset = mContext.getResources().getDimensionPixelSize( 4937 R.dimen.notification_reply_inset); 4938 // We're subtracting the inset of the reply icon to make sure it's 4939 // aligned nicely on the right, and remove it from the following padding 4940 marginEnd -= replyInset * 2; 4941 } 4942 if (largeIconShown) { 4943 // adding size of the right icon 4944 marginEnd += iconSize; 4945 4946 if (replyIconShown) { 4947 // We also add some padding to the reply icon if it's around 4948 marginEnd += contentMargin; 4949 } 4950 } 4951 if (replyIconShown || largeIconShown) { 4952 // The padding to the content 4953 marginEnd += contentMargin; 4954 } 4955 return marginEnd; 4956 } 4957 4958 /** 4959 * Bind the large icon. 4960 * @return if the largeIcon is visible 4961 */ bindLargeIcon(RemoteViews contentView, StandardTemplateParams p)4962 private boolean bindLargeIcon(RemoteViews contentView, StandardTemplateParams p) { 4963 if (mN.mLargeIcon == null && mN.largeIcon != null) { 4964 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 4965 } 4966 boolean showLargeIcon = mN.mLargeIcon != null && !p.hideLargeIcon; 4967 if (showLargeIcon) { 4968 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 4969 contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon); 4970 processLargeLegacyIcon(mN.mLargeIcon, contentView, p); 4971 } 4972 return showLargeIcon; 4973 } 4974 4975 /** 4976 * Bind the reply icon. 4977 * @return if the reply icon is visible 4978 */ bindReplyIcon(RemoteViews contentView, StandardTemplateParams p)4979 private boolean bindReplyIcon(RemoteViews contentView, StandardTemplateParams p) { 4980 boolean actionVisible = !p.hideReplyIcon; 4981 Action action = null; 4982 if (actionVisible) { 4983 action = findReplyAction(); 4984 actionVisible = action != null; 4985 } 4986 if (actionVisible) { 4987 contentView.setViewVisibility(R.id.reply_icon_action, View.VISIBLE); 4988 contentView.setDrawableTint(R.id.reply_icon_action, 4989 false /* targetBackground */, 4990 getNeutralColor(p), 4991 PorterDuff.Mode.SRC_ATOP); 4992 contentView.setOnClickPendingIntent(R.id.reply_icon_action, action.actionIntent); 4993 contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs); 4994 } else { 4995 contentView.setRemoteInputs(R.id.reply_icon_action, null); 4996 } 4997 contentView.setViewVisibility(R.id.reply_icon_action, 4998 actionVisible ? View.VISIBLE : View.GONE); 4999 return actionVisible; 5000 } 5001 findReplyAction()5002 private Action findReplyAction() { 5003 ArrayList<Action> actions = mActions; 5004 if (mOriginalActions != null) { 5005 actions = mOriginalActions; 5006 } 5007 int numActions = actions.size(); 5008 for (int i = 0; i < numActions; i++) { 5009 Action action = actions.get(i); 5010 if (hasValidRemoteInput(action)) { 5011 return action; 5012 } 5013 } 5014 return null; 5015 } 5016 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5017 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 5018 bindSmallIcon(contentView, p); 5019 bindHeaderAppName(contentView, p); 5020 bindHeaderText(contentView, p); 5021 bindHeaderTextSecondary(contentView, p); 5022 bindHeaderChronometerAndTime(contentView, p); 5023 bindProfileBadge(contentView, p); 5024 bindAlertedIcon(contentView, p); 5025 bindActivePermissions(contentView, p); 5026 bindExpandButton(contentView, p); 5027 mN.mUsesStandardHeader = true; 5028 } 5029 bindActivePermissions(RemoteViews contentView, StandardTemplateParams p)5030 private void bindActivePermissions(RemoteViews contentView, StandardTemplateParams p) { 5031 int color = getNeutralColor(p); 5032 contentView.setDrawableTint(R.id.camera, false, color, PorterDuff.Mode.SRC_ATOP); 5033 contentView.setDrawableTint(R.id.mic, false, color, PorterDuff.Mode.SRC_ATOP); 5034 contentView.setDrawableTint(R.id.overlay, false, color, PorterDuff.Mode.SRC_ATOP); 5035 } 5036 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5037 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 5038 int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p); 5039 contentView.setDrawableTint(R.id.expand_button, false, color, 5040 PorterDuff.Mode.SRC_ATOP); 5041 contentView.setInt(R.id.notification_header, "setOriginalNotificationColor", 5042 color); 5043 } 5044 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p)5045 private void bindHeaderChronometerAndTime(RemoteViews contentView, 5046 StandardTemplateParams p) { 5047 if (showsTimeOrChronometer()) { 5048 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 5049 setTextViewColorSecondary(contentView, R.id.time_divider, p); 5050 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 5051 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 5052 contentView.setLong(R.id.chronometer, "setBase", 5053 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 5054 contentView.setBoolean(R.id.chronometer, "setStarted", true); 5055 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 5056 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 5057 setTextViewColorSecondary(contentView, R.id.chronometer, p); 5058 } else { 5059 contentView.setViewVisibility(R.id.time, View.VISIBLE); 5060 contentView.setLong(R.id.time, "setTime", mN.when); 5061 setTextViewColorSecondary(contentView, R.id.time, p); 5062 } 5063 } else { 5064 // We still want a time to be set but gone, such that we can show and hide it 5065 // on demand in case it's a child notification without anything in the header 5066 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 5067 } 5068 } 5069 bindHeaderText(RemoteViews contentView, StandardTemplateParams p)5070 private void bindHeaderText(RemoteViews contentView, StandardTemplateParams p) { 5071 CharSequence summaryText = p.summaryText; 5072 if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet 5073 && mStyle.hasSummaryInHeader()) { 5074 summaryText = mStyle.mSummaryText; 5075 } 5076 if (summaryText == null 5077 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 5078 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 5079 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 5080 } 5081 if (summaryText != null) { 5082 // TODO: Remove the span entirely to only have the string with propper formating. 5083 contentView.setTextViewText(R.id.header_text, processTextSpans( 5084 processLegacyText(summaryText))); 5085 setTextViewColorSecondary(contentView, R.id.header_text, p); 5086 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 5087 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 5088 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 5089 } 5090 } 5091 bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p)5092 private void bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p) { 5093 if (!TextUtils.isEmpty(p.headerTextSecondary)) { 5094 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( 5095 processLegacyText(p.headerTextSecondary))); 5096 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 5097 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 5098 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 5099 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 5100 } 5101 } 5102 5103 /** 5104 * @hide 5105 */ 5106 @UnsupportedAppUsage loadHeaderAppName()5107 public String loadHeaderAppName() { 5108 CharSequence name = null; 5109 final PackageManager pm = mContext.getPackageManager(); 5110 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 5111 // only system packages which lump together a bunch of unrelated stuff 5112 // may substitute a different name to make the purpose of the 5113 // notification more clear. the correct package label should always 5114 // be accessible via SystemUI. 5115 final String pkg = mContext.getPackageName(); 5116 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 5117 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 5118 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 5119 name = subName; 5120 } else { 5121 Log.w(TAG, "warning: pkg " 5122 + pkg + " attempting to substitute app name '" + subName 5123 + "' without holding perm " 5124 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 5125 } 5126 } 5127 if (TextUtils.isEmpty(name)) { 5128 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 5129 } 5130 if (TextUtils.isEmpty(name)) { 5131 // still nothing? 5132 return null; 5133 } 5134 5135 return String.valueOf(name); 5136 } bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p)5137 private void bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p) { 5138 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 5139 if (isColorized(p)) { 5140 setTextViewColorPrimary(contentView, R.id.app_name_text, p); 5141 } else { 5142 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 5143 } 5144 } 5145 isColorized(StandardTemplateParams p)5146 private boolean isColorized(StandardTemplateParams p) { 5147 return p.allowColorization && mN.isColorized(); 5148 } 5149 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5150 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 5151 if (mN.mSmallIcon == null && mN.icon != 0) { 5152 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 5153 } 5154 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 5155 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 5156 processSmallIconColor(mN.mSmallIcon, contentView, p); 5157 } 5158 5159 /** 5160 * @return true if the built notification will show the time or the chronometer; false 5161 * otherwise 5162 */ showsTimeOrChronometer()5163 private boolean showsTimeOrChronometer() { 5164 return mN.showsTime() || mN.showsChronometer(); 5165 } 5166 resetStandardTemplateWithActions(RemoteViews big)5167 private void resetStandardTemplateWithActions(RemoteViews big) { 5168 // actions_container is only reset when there are no actions to avoid focus issues with 5169 // remote inputs. 5170 big.setViewVisibility(R.id.actions, View.GONE); 5171 big.removeAllViews(R.id.actions); 5172 5173 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 5174 big.setTextViewText(R.id.notification_material_reply_text_1, null); 5175 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 5176 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 5177 5178 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 5179 big.setTextViewText(R.id.notification_material_reply_text_2, null); 5180 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 5181 big.setTextViewText(R.id.notification_material_reply_text_3, null); 5182 5183 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 5184 R.dimen.notification_content_margin); 5185 } 5186 applyStandardTemplateWithActions(int layoutId, TemplateBindResult result)5187 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5188 TemplateBindResult result) { 5189 return applyStandardTemplateWithActions(layoutId, mParams.reset().fillTextsFrom(this), 5190 result); 5191 } 5192 filterOutContextualActions( List<Notification.Action> actions)5193 private static List<Notification.Action> filterOutContextualActions( 5194 List<Notification.Action> actions) { 5195 List<Notification.Action> nonContextualActions = new ArrayList<>(); 5196 for (Notification.Action action : actions) { 5197 if (!action.isContextual()) { 5198 nonContextualActions.add(action); 5199 } 5200 } 5201 return nonContextualActions; 5202 } 5203 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5204 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5205 StandardTemplateParams p, TemplateBindResult result) { 5206 RemoteViews big = applyStandardTemplate(layoutId, p, result); 5207 5208 resetStandardTemplateWithActions(big); 5209 5210 boolean validRemoteInput = false; 5211 5212 // In the UI contextual actions appear separately from the standard actions, so we 5213 // filter them out here. 5214 List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions); 5215 5216 int N = nonContextualActions.size(); 5217 boolean emphazisedMode = mN.fullScreenIntent != null; 5218 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); 5219 if (N > 0) { 5220 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 5221 big.setViewVisibility(R.id.actions, View.VISIBLE); 5222 big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0); 5223 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; 5224 for (int i=0; i<N; i++) { 5225 Action action = nonContextualActions.get(i); 5226 5227 boolean actionHasValidInput = hasValidRemoteInput(action); 5228 validRemoteInput |= actionHasValidInput; 5229 5230 final RemoteViews button = generateActionButton(action, emphazisedMode, p); 5231 if (actionHasValidInput && !emphazisedMode) { 5232 // Clear the drawable 5233 button.setInt(R.id.action0, "setBackgroundResource", 0); 5234 } 5235 big.addView(R.id.actions, button); 5236 } 5237 } else { 5238 big.setViewVisibility(R.id.actions_container, View.GONE); 5239 } 5240 5241 CharSequence[] replyText = mN.extras.getCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY); 5242 if (validRemoteInput && replyText != null 5243 && replyText.length > 0 && !TextUtils.isEmpty(replyText[0]) 5244 && p.maxRemoteInputHistory > 0) { 5245 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 5246 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 5247 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 5248 View.VISIBLE); 5249 big.setTextViewText(R.id.notification_material_reply_text_1, 5250 processTextSpans(replyText[0])); 5251 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 5252 big.setViewVisibility(R.id.notification_material_reply_progress, 5253 showSpinner ? View.VISIBLE : View.GONE); 5254 big.setProgressIndeterminateTintList( 5255 R.id.notification_material_reply_progress, 5256 ColorStateList.valueOf( 5257 isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p))); 5258 5259 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1]) 5260 && p.maxRemoteInputHistory > 1) { 5261 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 5262 big.setTextViewText(R.id.notification_material_reply_text_2, 5263 processTextSpans(replyText[1])); 5264 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 5265 5266 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2]) 5267 && p.maxRemoteInputHistory > 2) { 5268 big.setViewVisibility( 5269 R.id.notification_material_reply_text_3, View.VISIBLE); 5270 big.setTextViewText(R.id.notification_material_reply_text_3, 5271 processTextSpans(replyText[2])); 5272 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 5273 } 5274 } 5275 } 5276 5277 return big; 5278 } 5279 hasValidRemoteInput(Action action)5280 private boolean hasValidRemoteInput(Action action) { 5281 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 5282 // Weird actions 5283 return false; 5284 } 5285 5286 RemoteInput[] remoteInputs = action.getRemoteInputs(); 5287 if (remoteInputs == null) { 5288 return false; 5289 } 5290 5291 for (RemoteInput r : remoteInputs) { 5292 CharSequence[] choices = r.getChoices(); 5293 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 5294 return true; 5295 } 5296 } 5297 return false; 5298 } 5299 5300 /** 5301 * Construct a RemoteViews for the final 1U notification layout. In order: 5302 * 1. Custom contentView from the caller 5303 * 2. Style's proposed content view 5304 * 3. Standard template view 5305 */ createContentView()5306 public RemoteViews createContentView() { 5307 return createContentView(false /* increasedheight */ ); 5308 } 5309 5310 /** 5311 * Construct a RemoteViews for the smaller content view. 5312 * 5313 * @param increasedHeight true if this layout be created with an increased height. Some 5314 * styles may support showing more then just that basic 1U size 5315 * and the system may decide to render important notifications 5316 * slightly bigger even when collapsed. 5317 * 5318 * @hide 5319 */ createContentView(boolean increasedHeight)5320 public RemoteViews createContentView(boolean increasedHeight) { 5321 if (mN.contentView != null && useExistingRemoteView()) { 5322 return mN.contentView; 5323 } else if (mStyle != null) { 5324 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 5325 if (styleView != null) { 5326 return styleView; 5327 } 5328 } 5329 return applyStandardTemplate(getBaseLayoutResource(), null /* result */); 5330 } 5331 useExistingRemoteView()5332 private boolean useExistingRemoteView() { 5333 return mStyle == null || (!mStyle.displayCustomViewInline() 5334 && !mRebuildStyledRemoteViews); 5335 } 5336 5337 /** 5338 * Construct a RemoteViews for the final big notification layout. 5339 */ createBigContentView()5340 public RemoteViews createBigContentView() { 5341 RemoteViews result = null; 5342 if (mN.bigContentView != null && useExistingRemoteView()) { 5343 return mN.bigContentView; 5344 } else if (mStyle != null) { 5345 result = mStyle.makeBigContentView(); 5346 hideLine1Text(result); 5347 } else if (mActions.size() != 0) { 5348 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5349 null /* result */); 5350 } 5351 makeHeaderExpanded(result); 5352 return result; 5353 } 5354 5355 /** 5356 * Construct a RemoteViews for the final notification header only. This will not be 5357 * colorized. 5358 * 5359 * @hide 5360 */ makeNotificationHeader()5361 public RemoteViews makeNotificationHeader() { 5362 return makeNotificationHeader(mParams.reset().fillTextsFrom(this)); 5363 } 5364 5365 /** 5366 * Construct a RemoteViews for the final notification header only. This will not be 5367 * colorized. 5368 * 5369 * @param p the template params to inflate this with 5370 */ makeNotificationHeader(StandardTemplateParams p)5371 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 5372 // Headers on their own are never colorized 5373 p.disallowColorization(); 5374 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 5375 R.layout.notification_template_header); 5376 resetNotificationHeader(header); 5377 bindNotificationHeader(header, p); 5378 return header; 5379 } 5380 5381 /** 5382 * Construct a RemoteViews for the ambient version of the notification. 5383 * 5384 * @hide 5385 */ makeAmbientNotification()5386 public RemoteViews makeAmbientNotification() { 5387 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 5388 if (headsUpContentView != null) { 5389 return headsUpContentView; 5390 } 5391 return createContentView(); 5392 } 5393 hideLine1Text(RemoteViews result)5394 private void hideLine1Text(RemoteViews result) { 5395 if (result != null) { 5396 result.setViewVisibility(R.id.text_line_1, View.GONE); 5397 } 5398 } 5399 5400 /** 5401 * Adapt the Notification header if this view is used as an expanded view. 5402 * 5403 * @hide 5404 */ makeHeaderExpanded(RemoteViews result)5405 public static void makeHeaderExpanded(RemoteViews result) { 5406 if (result != null) { 5407 result.setBoolean(R.id.notification_header, "setExpanded", true); 5408 } 5409 } 5410 5411 /** 5412 * Construct a RemoteViews for the final heads-up notification layout. 5413 * 5414 * @param increasedHeight true if this layout be created with an increased height. Some 5415 * styles may support showing more then just that basic 1U size 5416 * and the system may decide to render important notifications 5417 * slightly bigger even when collapsed. 5418 * 5419 * @hide 5420 */ createHeadsUpContentView(boolean increasedHeight)5421 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 5422 if (mN.headsUpContentView != null && useExistingRemoteView()) { 5423 return mN.headsUpContentView; 5424 } else if (mStyle != null) { 5425 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 5426 if (styleView != null) { 5427 return styleView; 5428 } 5429 } else if (mActions.size() == 0) { 5430 return null; 5431 } 5432 5433 // We only want at most a single remote input history to be shown here, otherwise 5434 // the content would become squished. 5435 StandardTemplateParams p = mParams.reset().fillTextsFrom(this) 5436 .setMaxRemoteInputHistory(1); 5437 return applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5438 p, 5439 null /* result */); 5440 } 5441 5442 /** 5443 * Construct a RemoteViews for the final heads-up notification layout. 5444 */ createHeadsUpContentView()5445 public RemoteViews createHeadsUpContentView() { 5446 return createHeadsUpContentView(false /* useIncreasedHeight */); 5447 } 5448 5449 /** 5450 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 5451 * 5452 * @param isLowPriority is this notification low priority 5453 * @hide 5454 */ 5455 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)5456 public RemoteViews makePublicContentView(boolean isLowPriority) { 5457 if (mN.publicVersion != null) { 5458 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 5459 return builder.createContentView(); 5460 } 5461 Bundle savedBundle = mN.extras; 5462 Style style = mStyle; 5463 mStyle = null; 5464 Icon largeIcon = mN.mLargeIcon; 5465 mN.mLargeIcon = null; 5466 Bitmap largeIconLegacy = mN.largeIcon; 5467 mN.largeIcon = null; 5468 ArrayList<Action> actions = mActions; 5469 mActions = new ArrayList<>(); 5470 Bundle publicExtras = new Bundle(); 5471 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 5472 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 5473 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 5474 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 5475 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 5476 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 5477 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 5478 if (appName != null) { 5479 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 5480 } 5481 mN.extras = publicExtras; 5482 RemoteViews view; 5483 StandardTemplateParams params = mParams.reset().fillTextsFrom(this); 5484 if (isLowPriority) { 5485 params.forceDefaultColor(); 5486 } 5487 view = makeNotificationHeader(params); 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#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#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 return makeMediaContentView(); 8122 } 8123 8124 /** @hide */ 8125 @Override addExtras(Bundle extras)8126 public void addExtras(Bundle extras) { 8127 super.addExtras(extras); 8128 8129 if (mToken != null) { 8130 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 8131 } 8132 if (mActionsToShowInCompact != null) { 8133 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 8134 } 8135 } 8136 8137 /** 8138 * @hide 8139 */ 8140 @Override restoreFromExtras(Bundle extras)8141 protected void restoreFromExtras(Bundle extras) { 8142 super.restoreFromExtras(extras); 8143 8144 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 8145 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); 8146 } 8147 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 8148 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 8149 } 8150 } 8151 8152 /** 8153 * @hide 8154 */ 8155 @Override areNotificationsVisiblyDifferent(Style other)8156 public boolean areNotificationsVisiblyDifferent(Style other) { 8157 if (other == null || getClass() != other.getClass()) { 8158 return true; 8159 } 8160 // All fields to compare are on the Notification object 8161 return false; 8162 } 8163 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)8164 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 8165 Action action, StandardTemplateParams p) { 8166 final boolean tombstone = (action.actionIntent == null); 8167 container.setViewVisibility(buttonId, View.VISIBLE); 8168 container.setImageViewIcon(buttonId, action.getIcon()); 8169 8170 // If the action buttons should not be tinted, then just use the default 8171 // notification color. Otherwise, just use the passed-in color. 8172 Resources resources = mBuilder.mContext.getResources(); 8173 Configuration currentConfig = resources.getConfiguration(); 8174 boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 8175 == Configuration.UI_MODE_NIGHT_YES; 8176 int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized(p) 8177 ? getActionColor(p) 8178 : ContrastColorUtil.resolveColor(mBuilder.mContext, 8179 Notification.COLOR_DEFAULT, inNightMode); 8180 8181 container.setDrawableTint(buttonId, false, tintColor, 8182 PorterDuff.Mode.SRC_ATOP); 8183 8184 final TypedArray typedArray = mBuilder.mContext.obtainStyledAttributes( 8185 new int[]{ android.R.attr.colorControlHighlight }); 8186 int rippleAlpha = Color.alpha(typedArray.getColor(0, 0)); 8187 typedArray.recycle(); 8188 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 8189 Color.blue(tintColor)); 8190 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 8191 8192 if (!tombstone) { 8193 container.setOnClickPendingIntent(buttonId, action.actionIntent); 8194 } 8195 container.setContentDescription(buttonId, action.title); 8196 } 8197 makeMediaContentView()8198 private RemoteViews makeMediaContentView() { 8199 StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom( 8200 mBuilder); 8201 RemoteViews view = mBuilder.applyStandardTemplate( 8202 R.layout.notification_template_material_media, p, 8203 null /* result */); 8204 8205 final int numActions = mBuilder.mActions.size(); 8206 final int numActionsToShow = mActionsToShowInCompact == null 8207 ? 0 8208 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 8209 if (numActionsToShow > numActions) { 8210 throw new IllegalArgumentException(String.format( 8211 "setShowActionsInCompactView: action %d out of bounds (max %d)", 8212 numActions, numActions - 1)); 8213 } 8214 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 8215 if (i < numActionsToShow) { 8216 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 8217 bindMediaActionButton(view, MEDIA_BUTTON_IDS[i], action, p); 8218 } else { 8219 view.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 8220 } 8221 } 8222 handleImage(view); 8223 // handle the content margin 8224 int endMargin = R.dimen.notification_content_margin_end; 8225 if (mBuilder.mN.hasLargeIcon()) { 8226 endMargin = R.dimen.notification_media_image_margin_end; 8227 } 8228 view.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin); 8229 return view; 8230 } 8231 getActionColor(StandardTemplateParams p)8232 private int getActionColor(StandardTemplateParams p) { 8233 return mBuilder.isColorized(p) ? mBuilder.getPrimaryTextColor(p) 8234 : mBuilder.resolveContrastColor(p); 8235 } 8236 makeMediaBigContentView()8237 private RemoteViews makeMediaBigContentView() { 8238 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 8239 // Dont add an expanded view if there is no more content to be revealed 8240 int actionsInCompact = mActionsToShowInCompact == null 8241 ? 0 8242 : Math.min(mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 8243 if (!mBuilder.mN.hasLargeIcon() && actionCount <= actionsInCompact) { 8244 return null; 8245 } 8246 StandardTemplateParams p = mBuilder.mParams.reset().hasProgress(false).fillTextsFrom( 8247 mBuilder); 8248 RemoteViews big = mBuilder.applyStandardTemplate( 8249 R.layout.notification_template_material_big_media, p , null /* result */); 8250 8251 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 8252 if (i < actionCount) { 8253 bindMediaActionButton(big, MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 8254 } else { 8255 big.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 8256 } 8257 } 8258 bindMediaActionButton(big, R.id.media_seamless, new Action(R.drawable.ic_media_seamless, 8259 mBuilder.mContext.getString( 8260 com.android.internal.R.string.ext_media_seamless_action), null), p); 8261 big.setViewVisibility(R.id.media_seamless, View.GONE); 8262 handleImage(big); 8263 return big; 8264 } 8265 handleImage(RemoteViews contentView)8266 private void handleImage(RemoteViews contentView) { 8267 if (mBuilder.mN.hasLargeIcon()) { 8268 contentView.setViewLayoutMarginEndDimen(R.id.line1, 0); 8269 contentView.setViewLayoutMarginEndDimen(R.id.text, 0); 8270 } 8271 } 8272 8273 /** 8274 * @hide 8275 */ 8276 @Override hasProgress()8277 protected boolean hasProgress() { 8278 return false; 8279 } 8280 } 8281 8282 /** 8283 * Notification style for custom views that are decorated by the system 8284 * 8285 * <p>Instead of providing a notification that is completely custom, a developer can set this 8286 * style and still obtain system decorations like the notification header with the expand 8287 * affordance and actions. 8288 * 8289 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 8290 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 8291 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 8292 * corresponding custom views to display. 8293 * 8294 * To use this style with your Notification, feed it to 8295 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8296 * <pre class="prettyprint"> 8297 * Notification noti = new Notification.Builder() 8298 * .setSmallIcon(R.drawable.ic_stat_player) 8299 * .setLargeIcon(albumArtBitmap)) 8300 * .setCustomContentView(contentView); 8301 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 8302 * .build(); 8303 * </pre> 8304 */ 8305 public static class DecoratedCustomViewStyle extends Style { 8306 DecoratedCustomViewStyle()8307 public DecoratedCustomViewStyle() { 8308 } 8309 8310 /** 8311 * @hide 8312 */ displayCustomViewInline()8313 public boolean displayCustomViewInline() { 8314 return true; 8315 } 8316 8317 /** 8318 * @hide 8319 */ 8320 @Override makeContentView(boolean increasedHeight)8321 public RemoteViews makeContentView(boolean increasedHeight) { 8322 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 8323 } 8324 8325 /** 8326 * @hide 8327 */ 8328 @Override makeBigContentView()8329 public RemoteViews makeBigContentView() { 8330 return makeDecoratedBigContentView(); 8331 } 8332 8333 /** 8334 * @hide 8335 */ 8336 @Override makeHeadsUpContentView(boolean increasedHeight)8337 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8338 return makeDecoratedHeadsUpContentView(); 8339 } 8340 makeDecoratedHeadsUpContentView()8341 private RemoteViews makeDecoratedHeadsUpContentView() { 8342 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 8343 ? mBuilder.mN.contentView 8344 : mBuilder.mN.headsUpContentView; 8345 if (mBuilder.mActions.size() == 0) { 8346 return makeStandardTemplateWithCustomContent(headsUpContentView); 8347 } 8348 TemplateBindResult result = new TemplateBindResult(); 8349 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 8350 mBuilder.getBigBaseLayoutResource(), result); 8351 buildIntoRemoteViewContent(remoteViews, headsUpContentView, result); 8352 return remoteViews; 8353 } 8354 makeStandardTemplateWithCustomContent(RemoteViews customContent)8355 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 8356 TemplateBindResult result = new TemplateBindResult(); 8357 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 8358 mBuilder.getBaseLayoutResource(), result); 8359 buildIntoRemoteViewContent(remoteViews, customContent, result); 8360 return remoteViews; 8361 } 8362 makeDecoratedBigContentView()8363 private RemoteViews makeDecoratedBigContentView() { 8364 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 8365 ? mBuilder.mN.contentView 8366 : mBuilder.mN.bigContentView; 8367 if (mBuilder.mActions.size() == 0) { 8368 return makeStandardTemplateWithCustomContent(bigContentView); 8369 } 8370 TemplateBindResult result = new TemplateBindResult(); 8371 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 8372 mBuilder.getBigBaseLayoutResource(), result); 8373 buildIntoRemoteViewContent(remoteViews, bigContentView, result); 8374 return remoteViews; 8375 } 8376 buildIntoRemoteViewContent(RemoteViews remoteViews, RemoteViews customContent, TemplateBindResult result)8377 private void buildIntoRemoteViewContent(RemoteViews remoteViews, 8378 RemoteViews customContent, TemplateBindResult result) { 8379 int childIndex = -1; 8380 if (customContent != null) { 8381 // Need to clone customContent before adding, because otherwise it can no longer be 8382 // parceled independently of remoteViews. 8383 customContent = customContent.clone(); 8384 remoteViews.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 8385 remoteViews.addView(R.id.notification_main_column, customContent, 0 /* index */); 8386 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 8387 childIndex = 0; 8388 } 8389 remoteViews.setIntTag(R.id.notification_main_column, 8390 com.android.internal.R.id.notification_custom_view_index_tag, 8391 childIndex); 8392 // also update the end margin if there is an image 8393 Resources resources = mBuilder.mContext.getResources(); 8394 int endMargin = resources.getDimensionPixelSize( 8395 R.dimen.notification_content_margin_end) + result.getIconMarginEnd(); 8396 remoteViews.setViewLayoutMarginEnd(R.id.notification_main_column, endMargin); 8397 } 8398 8399 /** 8400 * @hide 8401 */ 8402 @Override areNotificationsVisiblyDifferent(Style other)8403 public boolean areNotificationsVisiblyDifferent(Style other) { 8404 if (other == null || getClass() != other.getClass()) { 8405 return true; 8406 } 8407 // Comparison done for all custom RemoteViews, independent of style 8408 return false; 8409 } 8410 } 8411 8412 /** 8413 * Notification style for media custom views that are decorated by the system 8414 * 8415 * <p>Instead of providing a media notification that is completely custom, a developer can set 8416 * this style and still obtain system decorations like the notification header with the expand 8417 * affordance and actions. 8418 * 8419 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 8420 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 8421 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 8422 * corresponding custom views to display. 8423 * <p> 8424 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 8425 * notification by using {@link Notification.Builder#setColorized(boolean)}. 8426 * <p> 8427 * To use this style with your Notification, feed it to 8428 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8429 * <pre class="prettyprint"> 8430 * Notification noti = new Notification.Builder() 8431 * .setSmallIcon(R.drawable.ic_stat_player) 8432 * .setLargeIcon(albumArtBitmap)) 8433 * .setCustomContentView(contentView); 8434 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 8435 * .setMediaSession(mySession)) 8436 * .build(); 8437 * </pre> 8438 * 8439 * @see android.app.Notification.DecoratedCustomViewStyle 8440 * @see android.app.Notification.MediaStyle 8441 */ 8442 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 8443 DecoratedMediaCustomViewStyle()8444 public DecoratedMediaCustomViewStyle() { 8445 } 8446 8447 /** 8448 * @hide 8449 */ displayCustomViewInline()8450 public boolean displayCustomViewInline() { 8451 return true; 8452 } 8453 8454 /** 8455 * @hide 8456 */ 8457 @Override makeContentView(boolean increasedHeight)8458 public RemoteViews makeContentView(boolean increasedHeight) { 8459 RemoteViews remoteViews = super.makeContentView(false /* increasedHeight */); 8460 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 8461 mBuilder.mN.contentView); 8462 } 8463 8464 /** 8465 * @hide 8466 */ 8467 @Override makeBigContentView()8468 public RemoteViews makeBigContentView() { 8469 RemoteViews customRemoteView = mBuilder.mN.bigContentView != null 8470 ? mBuilder.mN.bigContentView 8471 : mBuilder.mN.contentView; 8472 return makeBigContentViewWithCustomContent(customRemoteView); 8473 } 8474 makeBigContentViewWithCustomContent(RemoteViews customRemoteView)8475 private RemoteViews makeBigContentViewWithCustomContent(RemoteViews customRemoteView) { 8476 RemoteViews remoteViews = super.makeBigContentView(); 8477 if (remoteViews != null) { 8478 return buildIntoRemoteView(remoteViews, R.id.notification_main_column, 8479 customRemoteView); 8480 } else if (customRemoteView != mBuilder.mN.contentView){ 8481 remoteViews = super.makeContentView(false /* increasedHeight */); 8482 return buildIntoRemoteView(remoteViews, R.id.notification_content_container, 8483 customRemoteView); 8484 } else { 8485 return null; 8486 } 8487 } 8488 8489 /** 8490 * @hide 8491 */ 8492 @Override makeHeadsUpContentView(boolean increasedHeight)8493 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8494 RemoteViews customRemoteView = mBuilder.mN.headsUpContentView != null 8495 ? mBuilder.mN.headsUpContentView 8496 : mBuilder.mN.contentView; 8497 return makeBigContentViewWithCustomContent(customRemoteView); 8498 } 8499 8500 /** 8501 * @hide 8502 */ 8503 @Override areNotificationsVisiblyDifferent(Style other)8504 public boolean areNotificationsVisiblyDifferent(Style other) { 8505 if (other == null || getClass() != other.getClass()) { 8506 return true; 8507 } 8508 // Comparison done for all custom RemoteViews, independent of style 8509 return false; 8510 } 8511 buildIntoRemoteView(RemoteViews remoteViews, int id, RemoteViews customContent)8512 private RemoteViews buildIntoRemoteView(RemoteViews remoteViews, int id, 8513 RemoteViews customContent) { 8514 if (customContent != null) { 8515 // Need to clone customContent before adding, because otherwise it can no longer be 8516 // parceled independently of remoteViews. 8517 customContent = customContent.clone(); 8518 customContent.overrideTextColors(mBuilder.getPrimaryTextColor(mBuilder.mParams)); 8519 remoteViews.removeAllViews(id); 8520 remoteViews.addView(id, customContent); 8521 remoteViews.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 8522 } 8523 return remoteViews; 8524 } 8525 } 8526 8527 /** 8528 * Encapsulates the information needed to display a notification as a bubble. 8529 * 8530 * <p>A bubble is used to display app content in a floating window over the existing 8531 * foreground activity. A bubble has a collapsed state represented by an icon, 8532 * {@link BubbleMetadata.Builder#setIcon(Icon)} and an expanded state which is populated 8533 * via {@link BubbleMetadata.Builder#setIntent(PendingIntent)}.</p> 8534 * 8535 * <b>Notifications with a valid and allowed bubble will display in collapsed state 8536 * outside of the notification shade on unlocked devices. When a user interacts with the 8537 * collapsed bubble, the bubble intent will be invoked and displayed.</b> 8538 * 8539 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 8540 */ 8541 public static final class BubbleMetadata implements Parcelable { 8542 8543 private PendingIntent mPendingIntent; 8544 private PendingIntent mDeleteIntent; 8545 private Icon mIcon; 8546 private int mDesiredHeight; 8547 @DimenRes private int mDesiredHeightResId; 8548 private int mFlags; 8549 8550 /** 8551 * If set and the app creating the bubble is in the foreground, the bubble will be posted 8552 * in its expanded state, with the contents of {@link #getIntent()} in a floating window. 8553 * 8554 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 8555 * The app is considered foreground if it is visible and on the screen, note that 8556 * a foreground service does not qualify. 8557 * </p> 8558 * 8559 * <p>Generally this flag should only be set if the user has performed an action to request 8560 * or create a bubble.</p> 8561 * 8562 * @hide 8563 */ 8564 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 8565 8566 /** 8567 * If set and the app posting the bubble is in the foreground, the bubble will 8568 * be posted <b>without</b> the associated notification in the notification shade. 8569 * 8570 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 8571 * The app is considered foreground if it is visible and on the screen, note that 8572 * a foreground service does not qualify. 8573 * </p> 8574 * 8575 * <p>Generally this flag should only be set if the user has performed an action to request 8576 * or create a bubble, or if the user has seen the content in the notification and the 8577 * notification is no longer relevant.</p> 8578 * 8579 * @hide 8580 */ 8581 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 8582 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId)8583 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 8584 Icon icon, int height, @DimenRes int heightResId) { 8585 mPendingIntent = expandIntent; 8586 mIcon = icon; 8587 mDesiredHeight = height; 8588 mDesiredHeightResId = heightResId; 8589 mDeleteIntent = deleteIntent; 8590 } 8591 BubbleMetadata(Parcel in)8592 private BubbleMetadata(Parcel in) { 8593 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 8594 mIcon = Icon.CREATOR.createFromParcel(in); 8595 mDesiredHeight = in.readInt(); 8596 mFlags = in.readInt(); 8597 if (in.readInt() != 0) { 8598 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 8599 } 8600 mDesiredHeightResId = in.readInt(); 8601 } 8602 8603 /** 8604 * @return the pending intent used to populate the floating window for this bubble. 8605 */ 8606 @NonNull getIntent()8607 public PendingIntent getIntent() { 8608 return mPendingIntent; 8609 } 8610 8611 /** 8612 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 8613 */ 8614 @Nullable getDeleteIntent()8615 public PendingIntent getDeleteIntent() { 8616 return mDeleteIntent; 8617 } 8618 8619 /** 8620 * @return the icon that will be displayed for this bubble when it is collapsed. 8621 */ 8622 @NonNull getIcon()8623 public Icon getIcon() { 8624 return mIcon; 8625 } 8626 8627 /** 8628 * @return the ideal height, in DPs, for the floating window that app content defined by 8629 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has not 8630 * been set. 8631 */ 8632 @Dimension(unit = DP) getDesiredHeight()8633 public int getDesiredHeight() { 8634 return mDesiredHeight; 8635 } 8636 8637 /** 8638 * @return the resId of ideal height for the floating window that app content defined by 8639 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 8640 * been provided for the desired height. 8641 */ 8642 @DimenRes getDesiredHeightResId()8643 public int getDesiredHeightResId() { 8644 return mDesiredHeightResId; 8645 } 8646 8647 /** 8648 * @return whether this bubble should auto expand when it is posted. 8649 * 8650 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 8651 */ getAutoExpandBubble()8652 public boolean getAutoExpandBubble() { 8653 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 8654 } 8655 8656 /** 8657 * @return whether this bubble should suppress the notification when it is posted. 8658 * 8659 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 8660 */ isNotificationSuppressed()8661 public boolean isNotificationSuppressed() { 8662 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 8663 } 8664 8665 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 8666 new Parcelable.Creator<BubbleMetadata>() { 8667 8668 @Override 8669 public BubbleMetadata createFromParcel(Parcel source) { 8670 return new BubbleMetadata(source); 8671 } 8672 8673 @Override 8674 public BubbleMetadata[] newArray(int size) { 8675 return new BubbleMetadata[size]; 8676 } 8677 }; 8678 8679 @Override describeContents()8680 public int describeContents() { 8681 return 0; 8682 } 8683 8684 @Override writeToParcel(Parcel out, int flags)8685 public void writeToParcel(Parcel out, int flags) { 8686 mPendingIntent.writeToParcel(out, 0); 8687 mIcon.writeToParcel(out, 0); 8688 out.writeInt(mDesiredHeight); 8689 out.writeInt(mFlags); 8690 out.writeInt(mDeleteIntent != null ? 1 : 0); 8691 if (mDeleteIntent != null) { 8692 mDeleteIntent.writeToParcel(out, 0); 8693 } 8694 out.writeInt(mDesiredHeightResId); 8695 } 8696 8697 /** 8698 * @hide 8699 */ setFlags(int flags)8700 public void setFlags(int flags) { 8701 mFlags = flags; 8702 } 8703 8704 /** 8705 * @hide 8706 */ getFlags()8707 public int getFlags() { 8708 return mFlags; 8709 } 8710 8711 /** 8712 * Builder to construct a {@link BubbleMetadata} object. 8713 */ 8714 public static final class Builder { 8715 8716 private PendingIntent mPendingIntent; 8717 private Icon mIcon; 8718 private int mDesiredHeight; 8719 @DimenRes private int mDesiredHeightResId; 8720 private int mFlags; 8721 private PendingIntent mDeleteIntent; 8722 8723 /** 8724 * Constructs a new builder object. 8725 */ Builder()8726 public Builder() { 8727 } 8728 8729 /** 8730 * Sets the intent that will be used when the bubble is expanded. This will display the 8731 * app content in a floating window over the existing foreground activity. 8732 * 8733 * <p>An intent is required.</p> 8734 * 8735 * @throws IllegalArgumentException if intent is null 8736 */ 8737 @NonNull setIntent(@onNull PendingIntent intent)8738 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 8739 if (intent == null) { 8740 throw new IllegalArgumentException("Bubble requires non-null pending intent"); 8741 } 8742 mPendingIntent = intent; 8743 return this; 8744 } 8745 8746 /** 8747 * Sets the icon that will represent the bubble when it is collapsed. 8748 * 8749 * <p>An icon is required and should be representative of the content within the bubble. 8750 * If your app produces multiple bubbles, the image should be unique for each of them. 8751 * </p> 8752 * 8753 * <p>The shape of a bubble icon is adaptive and can match the device theme. 8754 * 8755 * If your icon is bitmap-based, you should create it using 8756 * {@link Icon#createWithAdaptiveBitmap(Bitmap)}, otherwise this method will throw. 8757 * 8758 * If your icon is not bitmap-based, you should expect that the icon will be tinted. 8759 * </p> 8760 * 8761 * @throws IllegalArgumentException if icon is null or a non-adaptive bitmap 8762 */ 8763 @NonNull setIcon(@onNull Icon icon)8764 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 8765 if (icon == null) { 8766 throw new IllegalArgumentException("Bubbles require non-null icon"); 8767 } 8768 if (icon.getType() == TYPE_BITMAP) { 8769 throw new IllegalArgumentException("When using bitmap based icons, Bubbles " 8770 + "require TYPE_ADAPTIVE_BITMAP, please use" 8771 + " Icon#createWithAdaptiveBitmap instead"); 8772 } 8773 mIcon = icon; 8774 return this; 8775 } 8776 8777 /** 8778 * Sets the desired height in DPs for the app content defined by 8779 * {@link #setIntent(PendingIntent)}. 8780 * 8781 * <p>This height may not be respected if there is not enough space on the screen or if 8782 * the provided height is too small to be useful.</p> 8783 * 8784 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 8785 * previous value set will be cleared after calling this method, and this value will 8786 * be used instead.</p> 8787 * 8788 * <p>A desired height (in DPs or via resID) is optional.</p> 8789 * 8790 * @see #setDesiredHeightResId(int) 8791 */ 8792 @NonNull setDesiredHeight(@imensionunit = DP) int height)8793 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 8794 mDesiredHeight = Math.max(height, 0); 8795 mDesiredHeightResId = 0; 8796 return this; 8797 } 8798 8799 8800 /** 8801 * Sets the desired height via resId for the app content defined by 8802 * {@link #setIntent(PendingIntent)}. 8803 * 8804 * <p>This height may not be respected if there is not enough space on the screen or if 8805 * the provided height is too small to be useful.</p> 8806 * 8807 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 8808 * previous value set will be cleared after calling this method, and this value will 8809 * be used instead.</p> 8810 * 8811 * <p>A desired height (in DPs or via resID) is optional.</p> 8812 * 8813 * @see #setDesiredHeight(int) 8814 */ 8815 @NonNull setDesiredHeightResId(@imenRes int heightResId)8816 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 8817 mDesiredHeightResId = heightResId; 8818 mDesiredHeight = 0; 8819 return this; 8820 } 8821 8822 /** 8823 * Sets whether the bubble will be posted in its expanded state (with the contents of 8824 * {@link #getIntent()} in a floating window). 8825 * 8826 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 8827 * The app is considered foreground if it is visible and on the screen, note that 8828 * a foreground service does not qualify. 8829 * </p> 8830 * 8831 * <p>Generally, this flag should only be set if the user has performed an action to 8832 * request or create a bubble.</p> 8833 * 8834 * <p>Setting this flag is optional; it defaults to false.</p> 8835 */ 8836 @NonNull setAutoExpandBubble(boolean shouldExpand)8837 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 8838 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 8839 return this; 8840 } 8841 8842 /** 8843 * Sets whether the bubble will be posted <b>without</b> the associated notification in 8844 * the notification shade. 8845 * 8846 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 8847 * The app is considered foreground if it is visible and on the screen, note that 8848 * a foreground service does not qualify. 8849 * </p> 8850 * 8851 * <p>Generally, this flag should only be set if the user has performed an action to 8852 * request or create a bubble, or if the user has seen the content in the notification 8853 * and the notification is no longer relevant.</p> 8854 * 8855 * <p>Setting this flag is optional; it defaults to false.</p> 8856 */ 8857 @NonNull setSuppressNotification(boolean shouldSuppressNotif)8858 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 8859 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 8860 return this; 8861 } 8862 8863 /** 8864 * Sets an intent to send when this bubble is explicitly removed by the user. 8865 * 8866 * <p>Setting a delete intent is optional.</p> 8867 */ 8868 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)8869 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 8870 mDeleteIntent = deleteIntent; 8871 return this; 8872 } 8873 8874 /** 8875 * Creates the {@link BubbleMetadata} defined by this builder. 8876 * 8877 * @throws IllegalStateException if {@link #setIntent(PendingIntent)} and/or 8878 * {@link #setIcon(Icon)} have not been called on this 8879 * builder. 8880 */ 8881 @NonNull build()8882 public BubbleMetadata build() { 8883 if (mPendingIntent == null) { 8884 throw new IllegalStateException("Must supply pending intent to bubble"); 8885 } 8886 if (mIcon == null) { 8887 throw new IllegalStateException("Must supply an icon for the bubble"); 8888 } 8889 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 8890 mIcon, mDesiredHeight, mDesiredHeightResId); 8891 data.setFlags(mFlags); 8892 return data; 8893 } 8894 8895 /** 8896 * @hide 8897 */ setFlag(int mask, boolean value)8898 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 8899 if (value) { 8900 mFlags |= mask; 8901 } else { 8902 mFlags &= ~mask; 8903 } 8904 return this; 8905 } 8906 } 8907 } 8908 8909 8910 // When adding a new Style subclass here, don't forget to update 8911 // Builder.getNotificationStyleClass. 8912 8913 /** 8914 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 8915 * metadata or change options on a notification builder. 8916 */ 8917 public interface Extender { 8918 /** 8919 * Apply this extender to a notification builder. 8920 * @param builder the builder to be modified. 8921 * @return the build object for chaining. 8922 */ extend(Builder builder)8923 public Builder extend(Builder builder); 8924 } 8925 8926 /** 8927 * Helper class to add wearable extensions to notifications. 8928 * <p class="note"> See 8929 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 8930 * for Android Wear</a> for more information on how to use this class. 8931 * <p> 8932 * To create a notification with wearable extensions: 8933 * <ol> 8934 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 8935 * properties. 8936 * <li>Create a {@link android.app.Notification.WearableExtender}. 8937 * <li>Set wearable-specific properties using the 8938 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 8939 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 8940 * notification. 8941 * <li>Post the notification to the notification system with the 8942 * {@code NotificationManager.notify(...)} methods. 8943 * </ol> 8944 * 8945 * <pre class="prettyprint"> 8946 * Notification notif = new Notification.Builder(mContext) 8947 * .setContentTitle("New mail from " + sender.toString()) 8948 * .setContentText(subject) 8949 * .setSmallIcon(R.drawable.new_mail) 8950 * .extend(new Notification.WearableExtender() 8951 * .setContentIcon(R.drawable.new_mail)) 8952 * .build(); 8953 * NotificationManager notificationManger = 8954 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 8955 * notificationManger.notify(0, notif);</pre> 8956 * 8957 * <p>Wearable extensions can be accessed on an existing notification by using the 8958 * {@code WearableExtender(Notification)} constructor, 8959 * and then using the {@code get} methods to access values. 8960 * 8961 * <pre class="prettyprint"> 8962 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 8963 * notification); 8964 * List<Notification> pages = wearableExtender.getPages();</pre> 8965 */ 8966 public static final class WearableExtender implements Extender { 8967 /** 8968 * Sentinel value for an action index that is unset. 8969 */ 8970 public static final int UNSET_ACTION_INDEX = -1; 8971 8972 /** 8973 * Size value for use with {@link #setCustomSizePreset} to show this notification with 8974 * default sizing. 8975 * <p>For custom display notifications created using {@link #setDisplayIntent}, 8976 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 8977 * on their content. 8978 * 8979 * @deprecated Display intents are no longer supported. 8980 */ 8981 @Deprecated 8982 public static final int SIZE_DEFAULT = 0; 8983 8984 /** 8985 * Size value for use with {@link #setCustomSizePreset} to show this notification 8986 * with an extra small size. 8987 * <p>This value is only applicable for custom display notifications created using 8988 * {@link #setDisplayIntent}. 8989 * 8990 * @deprecated Display intents are no longer supported. 8991 */ 8992 @Deprecated 8993 public static final int SIZE_XSMALL = 1; 8994 8995 /** 8996 * Size value for use with {@link #setCustomSizePreset} to show this notification 8997 * with a small size. 8998 * <p>This value is only applicable for custom display notifications created using 8999 * {@link #setDisplayIntent}. 9000 * 9001 * @deprecated Display intents are no longer supported. 9002 */ 9003 @Deprecated 9004 public static final int SIZE_SMALL = 2; 9005 9006 /** 9007 * Size value for use with {@link #setCustomSizePreset} to show this notification 9008 * with a medium size. 9009 * <p>This value is only applicable for custom display notifications created using 9010 * {@link #setDisplayIntent}. 9011 * 9012 * @deprecated Display intents are no longer supported. 9013 */ 9014 @Deprecated 9015 public static final int SIZE_MEDIUM = 3; 9016 9017 /** 9018 * Size value for use with {@link #setCustomSizePreset} to show this notification 9019 * with a large size. 9020 * <p>This value is only applicable for custom display notifications created using 9021 * {@link #setDisplayIntent}. 9022 * 9023 * @deprecated Display intents are no longer supported. 9024 */ 9025 @Deprecated 9026 public static final int SIZE_LARGE = 4; 9027 9028 /** 9029 * Size value for use with {@link #setCustomSizePreset} to show this notification 9030 * full screen. 9031 * <p>This value is only applicable for custom display notifications created using 9032 * {@link #setDisplayIntent}. 9033 * 9034 * @deprecated Display intents are no longer supported. 9035 */ 9036 @Deprecated 9037 public static final int SIZE_FULL_SCREEN = 5; 9038 9039 /** 9040 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 9041 * short amount of time when this notification is displayed on the screen. This 9042 * is the default value. 9043 * 9044 * @deprecated This feature is no longer supported. 9045 */ 9046 @Deprecated 9047 public static final int SCREEN_TIMEOUT_SHORT = 0; 9048 9049 /** 9050 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 9051 * for a longer amount of time when this notification is displayed on the screen. 9052 * 9053 * @deprecated This feature is no longer supported. 9054 */ 9055 @Deprecated 9056 public static final int SCREEN_TIMEOUT_LONG = -1; 9057 9058 /** Notification extra which contains wearable extensions */ 9059 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 9060 9061 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 9062 private static final String KEY_ACTIONS = "actions"; 9063 private static final String KEY_FLAGS = "flags"; 9064 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 9065 private static final String KEY_PAGES = "pages"; 9066 private static final String KEY_BACKGROUND = "background"; 9067 private static final String KEY_CONTENT_ICON = "contentIcon"; 9068 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 9069 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 9070 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 9071 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 9072 private static final String KEY_GRAVITY = "gravity"; 9073 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 9074 private static final String KEY_DISMISSAL_ID = "dismissalId"; 9075 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 9076 9077 // Flags bitwise-ored to mFlags 9078 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 9079 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 9080 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 9081 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 9082 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 9083 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 9084 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 9085 9086 // Default value for flags integer 9087 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 9088 9089 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 9090 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 9091 9092 private ArrayList<Action> mActions = new ArrayList<Action>(); 9093 private int mFlags = DEFAULT_FLAGS; 9094 private PendingIntent mDisplayIntent; 9095 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 9096 private Bitmap mBackground; 9097 private int mContentIcon; 9098 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 9099 private int mContentActionIndex = UNSET_ACTION_INDEX; 9100 private int mCustomSizePreset = SIZE_DEFAULT; 9101 private int mCustomContentHeight; 9102 private int mGravity = DEFAULT_GRAVITY; 9103 private int mHintScreenTimeout; 9104 private String mDismissalId; 9105 private String mBridgeTag; 9106 9107 /** 9108 * Create a {@link android.app.Notification.WearableExtender} with default 9109 * options. 9110 */ WearableExtender()9111 public WearableExtender() { 9112 } 9113 WearableExtender(Notification notif)9114 public WearableExtender(Notification notif) { 9115 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 9116 if (wearableBundle != null) { 9117 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 9118 if (actions != null) { 9119 mActions.addAll(actions); 9120 } 9121 9122 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 9123 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 9124 9125 Notification[] pages = getNotificationArrayFromBundle( 9126 wearableBundle, KEY_PAGES); 9127 if (pages != null) { 9128 Collections.addAll(mPages, pages); 9129 } 9130 9131 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 9132 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 9133 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 9134 DEFAULT_CONTENT_ICON_GRAVITY); 9135 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 9136 UNSET_ACTION_INDEX); 9137 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 9138 SIZE_DEFAULT); 9139 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 9140 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 9141 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 9142 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 9143 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 9144 } 9145 } 9146 9147 /** 9148 * Apply wearable extensions to a notification that is being built. This is typically 9149 * called by the {@link android.app.Notification.Builder#extend} method of 9150 * {@link android.app.Notification.Builder}. 9151 */ 9152 @Override extend(Notification.Builder builder)9153 public Notification.Builder extend(Notification.Builder builder) { 9154 Bundle wearableBundle = new Bundle(); 9155 9156 if (!mActions.isEmpty()) { 9157 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 9158 } 9159 if (mFlags != DEFAULT_FLAGS) { 9160 wearableBundle.putInt(KEY_FLAGS, mFlags); 9161 } 9162 if (mDisplayIntent != null) { 9163 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 9164 } 9165 if (!mPages.isEmpty()) { 9166 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 9167 new Notification[mPages.size()])); 9168 } 9169 if (mBackground != null) { 9170 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 9171 } 9172 if (mContentIcon != 0) { 9173 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 9174 } 9175 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 9176 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 9177 } 9178 if (mContentActionIndex != UNSET_ACTION_INDEX) { 9179 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 9180 mContentActionIndex); 9181 } 9182 if (mCustomSizePreset != SIZE_DEFAULT) { 9183 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 9184 } 9185 if (mCustomContentHeight != 0) { 9186 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 9187 } 9188 if (mGravity != DEFAULT_GRAVITY) { 9189 wearableBundle.putInt(KEY_GRAVITY, mGravity); 9190 } 9191 if (mHintScreenTimeout != 0) { 9192 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 9193 } 9194 if (mDismissalId != null) { 9195 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 9196 } 9197 if (mBridgeTag != null) { 9198 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 9199 } 9200 9201 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 9202 return builder; 9203 } 9204 9205 @Override clone()9206 public WearableExtender clone() { 9207 WearableExtender that = new WearableExtender(); 9208 that.mActions = new ArrayList<Action>(this.mActions); 9209 that.mFlags = this.mFlags; 9210 that.mDisplayIntent = this.mDisplayIntent; 9211 that.mPages = new ArrayList<Notification>(this.mPages); 9212 that.mBackground = this.mBackground; 9213 that.mContentIcon = this.mContentIcon; 9214 that.mContentIconGravity = this.mContentIconGravity; 9215 that.mContentActionIndex = this.mContentActionIndex; 9216 that.mCustomSizePreset = this.mCustomSizePreset; 9217 that.mCustomContentHeight = this.mCustomContentHeight; 9218 that.mGravity = this.mGravity; 9219 that.mHintScreenTimeout = this.mHintScreenTimeout; 9220 that.mDismissalId = this.mDismissalId; 9221 that.mBridgeTag = this.mBridgeTag; 9222 return that; 9223 } 9224 9225 /** 9226 * Add a wearable action to this notification. 9227 * 9228 * <p>When wearable actions are added using this method, the set of actions that 9229 * show on a wearable device splits from devices that only show actions added 9230 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 9231 * of which actions display on different devices. 9232 * 9233 * @param action the action to add to this notification 9234 * @return this object for method chaining 9235 * @see android.app.Notification.Action 9236 */ addAction(Action action)9237 public WearableExtender addAction(Action action) { 9238 mActions.add(action); 9239 return this; 9240 } 9241 9242 /** 9243 * Adds wearable actions to this notification. 9244 * 9245 * <p>When wearable actions are added using this method, the set of actions that 9246 * show on a wearable device splits from devices that only show actions added 9247 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 9248 * of which actions display on different devices. 9249 * 9250 * @param actions the actions to add to this notification 9251 * @return this object for method chaining 9252 * @see android.app.Notification.Action 9253 */ addActions(List<Action> actions)9254 public WearableExtender addActions(List<Action> actions) { 9255 mActions.addAll(actions); 9256 return this; 9257 } 9258 9259 /** 9260 * Clear all wearable actions present on this builder. 9261 * @return this object for method chaining. 9262 * @see #addAction 9263 */ clearActions()9264 public WearableExtender clearActions() { 9265 mActions.clear(); 9266 return this; 9267 } 9268 9269 /** 9270 * Get the wearable actions present on this notification. 9271 */ getActions()9272 public List<Action> getActions() { 9273 return mActions; 9274 } 9275 9276 /** 9277 * Set an intent to launch inside of an activity view when displaying 9278 * this notification. The {@link PendingIntent} provided should be for an activity. 9279 * 9280 * <pre class="prettyprint"> 9281 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 9282 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 9283 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT); 9284 * Notification notif = new Notification.Builder(context) 9285 * .extend(new Notification.WearableExtender() 9286 * .setDisplayIntent(displayPendingIntent) 9287 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 9288 * .build();</pre> 9289 * 9290 * <p>The activity to launch needs to allow embedding, must be exported, and 9291 * should have an empty task affinity. It is also recommended to use the device 9292 * default light theme. 9293 * 9294 * <p>Example AndroidManifest.xml entry: 9295 * <pre class="prettyprint"> 9296 * <activity android:name="com.example.MyDisplayActivity" 9297 * android:exported="true" 9298 * android:allowEmbedded="true" 9299 * android:taskAffinity="" 9300 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 9301 * 9302 * @param intent the {@link PendingIntent} for an activity 9303 * @return this object for method chaining 9304 * @see android.app.Notification.WearableExtender#getDisplayIntent 9305 * @deprecated Display intents are no longer supported. 9306 */ 9307 @Deprecated setDisplayIntent(PendingIntent intent)9308 public WearableExtender setDisplayIntent(PendingIntent intent) { 9309 mDisplayIntent = intent; 9310 return this; 9311 } 9312 9313 /** 9314 * Get the intent to launch inside of an activity view when displaying this 9315 * notification. This {@code PendingIntent} should be for an activity. 9316 * 9317 * @deprecated Display intents are no longer supported. 9318 */ 9319 @Deprecated getDisplayIntent()9320 public PendingIntent getDisplayIntent() { 9321 return mDisplayIntent; 9322 } 9323 9324 /** 9325 * Add an additional page of content to display with this notification. The current 9326 * notification forms the first page, and pages added using this function form 9327 * subsequent pages. This field can be used to separate a notification into multiple 9328 * sections. 9329 * 9330 * @param page the notification to add as another page 9331 * @return this object for method chaining 9332 * @see android.app.Notification.WearableExtender#getPages 9333 * @deprecated Multiple content pages are no longer supported. 9334 */ 9335 @Deprecated addPage(Notification page)9336 public WearableExtender addPage(Notification page) { 9337 mPages.add(page); 9338 return this; 9339 } 9340 9341 /** 9342 * Add additional pages of content to display with this notification. The current 9343 * notification forms the first page, and pages added using this function form 9344 * subsequent pages. This field can be used to separate a notification into multiple 9345 * sections. 9346 * 9347 * @param pages a list of notifications 9348 * @return this object for method chaining 9349 * @see android.app.Notification.WearableExtender#getPages 9350 * @deprecated Multiple content pages are no longer supported. 9351 */ 9352 @Deprecated addPages(List<Notification> pages)9353 public WearableExtender addPages(List<Notification> pages) { 9354 mPages.addAll(pages); 9355 return this; 9356 } 9357 9358 /** 9359 * Clear all additional pages present on this builder. 9360 * @return this object for method chaining. 9361 * @see #addPage 9362 * @deprecated Multiple content pages are no longer supported. 9363 */ 9364 @Deprecated clearPages()9365 public WearableExtender clearPages() { 9366 mPages.clear(); 9367 return this; 9368 } 9369 9370 /** 9371 * Get the array of additional pages of content for displaying this notification. The 9372 * current notification forms the first page, and elements within this array form 9373 * subsequent pages. This field can be used to separate a notification into multiple 9374 * sections. 9375 * @return the pages for this notification 9376 * @deprecated Multiple content pages are no longer supported. 9377 */ 9378 @Deprecated getPages()9379 public List<Notification> getPages() { 9380 return mPages; 9381 } 9382 9383 /** 9384 * Set a background image to be displayed behind the notification content. 9385 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 9386 * will work with any notification style. 9387 * 9388 * @param background the background bitmap 9389 * @return this object for method chaining 9390 * @see android.app.Notification.WearableExtender#getBackground 9391 * @deprecated Background images are no longer supported. 9392 */ 9393 @Deprecated setBackground(Bitmap background)9394 public WearableExtender setBackground(Bitmap background) { 9395 mBackground = background; 9396 return this; 9397 } 9398 9399 /** 9400 * Get a background image to be displayed behind the notification content. 9401 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 9402 * will work with any notification style. 9403 * 9404 * @return the background image 9405 * @see android.app.Notification.WearableExtender#setBackground 9406 * @deprecated Background images are no longer supported. 9407 */ 9408 @Deprecated getBackground()9409 public Bitmap getBackground() { 9410 return mBackground; 9411 } 9412 9413 /** 9414 * Set an icon that goes with the content of this notification. 9415 */ 9416 @Deprecated setContentIcon(int icon)9417 public WearableExtender setContentIcon(int icon) { 9418 mContentIcon = icon; 9419 return this; 9420 } 9421 9422 /** 9423 * Get an icon that goes with the content of this notification. 9424 */ 9425 @Deprecated getContentIcon()9426 public int getContentIcon() { 9427 return mContentIcon; 9428 } 9429 9430 /** 9431 * Set the gravity that the content icon should have within the notification display. 9432 * Supported values include {@link android.view.Gravity#START} and 9433 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 9434 * @see #setContentIcon 9435 */ 9436 @Deprecated setContentIconGravity(int contentIconGravity)9437 public WearableExtender setContentIconGravity(int contentIconGravity) { 9438 mContentIconGravity = contentIconGravity; 9439 return this; 9440 } 9441 9442 /** 9443 * Get the gravity that the content icon should have within the notification display. 9444 * Supported values include {@link android.view.Gravity#START} and 9445 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 9446 * @see #getContentIcon 9447 */ 9448 @Deprecated getContentIconGravity()9449 public int getContentIconGravity() { 9450 return mContentIconGravity; 9451 } 9452 9453 /** 9454 * Set an action from this notification's actions as the primary action. If the action has a 9455 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 9456 * directly on the notification. 9457 * 9458 * @param actionIndex The index of the primary action. 9459 * If wearable actions were added to the main notification, this index 9460 * will apply to that list, otherwise it will apply to the regular 9461 * actions list. 9462 */ setContentAction(int actionIndex)9463 public WearableExtender setContentAction(int actionIndex) { 9464 mContentActionIndex = actionIndex; 9465 return this; 9466 } 9467 9468 /** 9469 * Get the index of the notification action, if any, that was specified as the primary 9470 * action. 9471 * 9472 * <p>If wearable specific actions were added to the main notification, this index will 9473 * apply to that list, otherwise it will apply to the regular actions list. 9474 * 9475 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 9476 */ getContentAction()9477 public int getContentAction() { 9478 return mContentActionIndex; 9479 } 9480 9481 /** 9482 * Set the gravity that this notification should have within the available viewport space. 9483 * Supported values include {@link android.view.Gravity#TOP}, 9484 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 9485 * The default value is {@link android.view.Gravity#BOTTOM}. 9486 */ 9487 @Deprecated setGravity(int gravity)9488 public WearableExtender setGravity(int gravity) { 9489 mGravity = gravity; 9490 return this; 9491 } 9492 9493 /** 9494 * Get the gravity that this notification should have within the available viewport space. 9495 * Supported values include {@link android.view.Gravity#TOP}, 9496 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 9497 * The default value is {@link android.view.Gravity#BOTTOM}. 9498 */ 9499 @Deprecated getGravity()9500 public int getGravity() { 9501 return mGravity; 9502 } 9503 9504 /** 9505 * Set the custom size preset for the display of this notification out of the available 9506 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 9507 * {@link #SIZE_LARGE}. 9508 * <p>Some custom size presets are only applicable for custom display notifications created 9509 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 9510 * documentation for the preset in question. See also 9511 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 9512 */ 9513 @Deprecated setCustomSizePreset(int sizePreset)9514 public WearableExtender setCustomSizePreset(int sizePreset) { 9515 mCustomSizePreset = sizePreset; 9516 return this; 9517 } 9518 9519 /** 9520 * Get the custom size preset for the display of this notification out of the available 9521 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 9522 * {@link #SIZE_LARGE}. 9523 * <p>Some custom size presets are only applicable for custom display notifications created 9524 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 9525 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 9526 */ 9527 @Deprecated getCustomSizePreset()9528 public int getCustomSizePreset() { 9529 return mCustomSizePreset; 9530 } 9531 9532 /** 9533 * Set the custom height in pixels for the display of this notification's content. 9534 * <p>This option is only available for custom display notifications created 9535 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 9536 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 9537 * {@link #getCustomContentHeight}. 9538 */ 9539 @Deprecated setCustomContentHeight(int height)9540 public WearableExtender setCustomContentHeight(int height) { 9541 mCustomContentHeight = height; 9542 return this; 9543 } 9544 9545 /** 9546 * Get the custom height in pixels for the display of this notification's content. 9547 * <p>This option is only available for custom display notifications created 9548 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 9549 * {@link #setCustomContentHeight}. 9550 */ 9551 @Deprecated getCustomContentHeight()9552 public int getCustomContentHeight() { 9553 return mCustomContentHeight; 9554 } 9555 9556 /** 9557 * Set whether the scrolling position for the contents of this notification should start 9558 * at the bottom of the contents instead of the top when the contents are too long to 9559 * display within the screen. Default is false (start scroll at the top). 9560 */ setStartScrollBottom(boolean startScrollBottom)9561 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 9562 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 9563 return this; 9564 } 9565 9566 /** 9567 * Get whether the scrolling position for the contents of this notification should start 9568 * at the bottom of the contents instead of the top when the contents are too long to 9569 * display within the screen. Default is false (start scroll at the top). 9570 */ getStartScrollBottom()9571 public boolean getStartScrollBottom() { 9572 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 9573 } 9574 9575 /** 9576 * Set whether the content intent is available when the wearable device is not connected 9577 * to a companion device. The user can still trigger this intent when the wearable device 9578 * is offline, but a visual hint will indicate that the content intent may not be available. 9579 * Defaults to true. 9580 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)9581 public WearableExtender setContentIntentAvailableOffline( 9582 boolean contentIntentAvailableOffline) { 9583 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 9584 return this; 9585 } 9586 9587 /** 9588 * Get whether the content intent is available when the wearable device is not connected 9589 * to a companion device. The user can still trigger this intent when the wearable device 9590 * is offline, but a visual hint will indicate that the content intent may not be available. 9591 * Defaults to true. 9592 */ getContentIntentAvailableOffline()9593 public boolean getContentIntentAvailableOffline() { 9594 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 9595 } 9596 9597 /** 9598 * Set a hint that this notification's icon should not be displayed. 9599 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 9600 * @return this object for method chaining 9601 */ 9602 @Deprecated setHintHideIcon(boolean hintHideIcon)9603 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 9604 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 9605 return this; 9606 } 9607 9608 /** 9609 * Get a hint that this notification's icon should not be displayed. 9610 * @return {@code true} if this icon should not be displayed, false otherwise. 9611 * The default value is {@code false} if this was never set. 9612 */ 9613 @Deprecated getHintHideIcon()9614 public boolean getHintHideIcon() { 9615 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 9616 } 9617 9618 /** 9619 * Set a visual hint that only the background image of this notification should be 9620 * displayed, and other semantic content should be hidden. This hint is only applicable 9621 * to sub-pages added using {@link #addPage}. 9622 */ 9623 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)9624 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 9625 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 9626 return this; 9627 } 9628 9629 /** 9630 * Get a visual hint that only the background image of this notification should be 9631 * displayed, and other semantic content should be hidden. This hint is only applicable 9632 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 9633 */ 9634 @Deprecated getHintShowBackgroundOnly()9635 public boolean getHintShowBackgroundOnly() { 9636 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 9637 } 9638 9639 /** 9640 * Set a hint that this notification's background should not be clipped if possible, 9641 * and should instead be resized to fully display on the screen, retaining the aspect 9642 * ratio of the image. This can be useful for images like barcodes or qr codes. 9643 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 9644 * @return this object for method chaining 9645 */ 9646 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)9647 public WearableExtender setHintAvoidBackgroundClipping( 9648 boolean hintAvoidBackgroundClipping) { 9649 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 9650 return this; 9651 } 9652 9653 /** 9654 * Get a hint that this notification's background should not be clipped if possible, 9655 * and should instead be resized to fully display on the screen, retaining the aspect 9656 * ratio of the image. This can be useful for images like barcodes or qr codes. 9657 * @return {@code true} if it's ok if the background is clipped on the screen, false 9658 * otherwise. The default value is {@code false} if this was never set. 9659 */ 9660 @Deprecated getHintAvoidBackgroundClipping()9661 public boolean getHintAvoidBackgroundClipping() { 9662 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 9663 } 9664 9665 /** 9666 * Set a hint that the screen should remain on for at least this duration when 9667 * this notification is displayed on the screen. 9668 * @param timeout The requested screen timeout in milliseconds. Can also be either 9669 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 9670 * @return this object for method chaining 9671 */ 9672 @Deprecated setHintScreenTimeout(int timeout)9673 public WearableExtender setHintScreenTimeout(int timeout) { 9674 mHintScreenTimeout = timeout; 9675 return this; 9676 } 9677 9678 /** 9679 * Get the duration, in milliseconds, that the screen should remain on for 9680 * when this notification is displayed. 9681 * @return the duration in milliseconds if > 0, or either one of the sentinel values 9682 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 9683 */ 9684 @Deprecated getHintScreenTimeout()9685 public int getHintScreenTimeout() { 9686 return mHintScreenTimeout; 9687 } 9688 9689 /** 9690 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 9691 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 9692 * qr codes, as well as other simple black-and-white tickets. 9693 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 9694 * @return this object for method chaining 9695 * @deprecated This feature is no longer supported. 9696 */ 9697 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)9698 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 9699 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 9700 return this; 9701 } 9702 9703 /** 9704 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 9705 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 9706 * qr codes, as well as other simple black-and-white tickets. 9707 * @return {@code true} if it should be displayed in ambient, false otherwise 9708 * otherwise. The default value is {@code false} if this was never set. 9709 * @deprecated This feature is no longer supported. 9710 */ 9711 @Deprecated getHintAmbientBigPicture()9712 public boolean getHintAmbientBigPicture() { 9713 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 9714 } 9715 9716 /** 9717 * Set a hint that this notification's content intent will launch an {@link Activity} 9718 * directly, telling the platform that it can generate the appropriate transitions. 9719 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 9720 * an activity and transitions should be generated, false otherwise. 9721 * @return this object for method chaining 9722 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)9723 public WearableExtender setHintContentIntentLaunchesActivity( 9724 boolean hintContentIntentLaunchesActivity) { 9725 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 9726 return this; 9727 } 9728 9729 /** 9730 * Get a hint that this notification's content intent will launch an {@link Activity} 9731 * directly, telling the platform that it can generate the appropriate transitions 9732 * @return {@code true} if the content intent will launch an activity and transitions should 9733 * be generated, false otherwise. The default value is {@code false} if this was never set. 9734 */ getHintContentIntentLaunchesActivity()9735 public boolean getHintContentIntentLaunchesActivity() { 9736 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 9737 } 9738 9739 /** 9740 * Sets the dismissal id for this notification. If a notification is posted with a 9741 * dismissal id, then when that notification is canceled, notifications on other wearables 9742 * and the paired Android phone having that same dismissal id will also be canceled. See 9743 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 9744 * Notifications</a> for more information. 9745 * @param dismissalId the dismissal id of the notification. 9746 * @return this object for method chaining 9747 */ setDismissalId(String dismissalId)9748 public WearableExtender setDismissalId(String dismissalId) { 9749 mDismissalId = dismissalId; 9750 return this; 9751 } 9752 9753 /** 9754 * Returns the dismissal id of the notification. 9755 * @return the dismissal id of the notification or null if it has not been set. 9756 */ getDismissalId()9757 public String getDismissalId() { 9758 return mDismissalId; 9759 } 9760 9761 /** 9762 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 9763 * posted from a phone to provide finer-grained control on what notifications are bridged 9764 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 9765 * Features to Notifications</a> for more information. 9766 * @param bridgeTag the bridge tag of the notification. 9767 * @return this object for method chaining 9768 */ setBridgeTag(String bridgeTag)9769 public WearableExtender setBridgeTag(String bridgeTag) { 9770 mBridgeTag = bridgeTag; 9771 return this; 9772 } 9773 9774 /** 9775 * Returns the bridge tag of the notification. 9776 * @return the bridge tag or null if not present. 9777 */ getBridgeTag()9778 public String getBridgeTag() { 9779 return mBridgeTag; 9780 } 9781 setFlag(int mask, boolean value)9782 private void setFlag(int mask, boolean value) { 9783 if (value) { 9784 mFlags |= mask; 9785 } else { 9786 mFlags &= ~mask; 9787 } 9788 } 9789 } 9790 9791 /** 9792 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 9793 * with car extensions: 9794 * 9795 * <ol> 9796 * <li>Create an {@link Notification.Builder}, setting any desired 9797 * properties. 9798 * <li>Create a {@link CarExtender}. 9799 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 9800 * {@link CarExtender}. 9801 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 9802 * to apply the extensions to a notification. 9803 * </ol> 9804 * 9805 * <pre class="prettyprint"> 9806 * Notification notification = new Notification.Builder(context) 9807 * ... 9808 * .extend(new CarExtender() 9809 * .set*(...)) 9810 * .build(); 9811 * </pre> 9812 * 9813 * <p>Car extensions can be accessed on an existing notification by using the 9814 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 9815 * to access values. 9816 */ 9817 public static final class CarExtender implements Extender { 9818 private static final String TAG = "CarExtender"; 9819 9820 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 9821 private static final String EXTRA_LARGE_ICON = "large_icon"; 9822 private static final String EXTRA_CONVERSATION = "car_conversation"; 9823 private static final String EXTRA_COLOR = "app_color"; 9824 9825 private Bitmap mLargeIcon; 9826 private UnreadConversation mUnreadConversation; 9827 private int mColor = Notification.COLOR_DEFAULT; 9828 9829 /** 9830 * Create a {@link CarExtender} with default options. 9831 */ CarExtender()9832 public CarExtender() { 9833 } 9834 9835 /** 9836 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 9837 * 9838 * @param notif The notification from which to copy options. 9839 */ CarExtender(Notification notif)9840 public CarExtender(Notification notif) { 9841 Bundle carBundle = notif.extras == null ? 9842 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 9843 if (carBundle != null) { 9844 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 9845 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 9846 9847 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 9848 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 9849 } 9850 } 9851 9852 /** 9853 * Apply car extensions to a notification that is being built. This is typically called by 9854 * the {@link Notification.Builder#extend(Notification.Extender)} 9855 * method of {@link Notification.Builder}. 9856 */ 9857 @Override extend(Notification.Builder builder)9858 public Notification.Builder extend(Notification.Builder builder) { 9859 Bundle carExtensions = new Bundle(); 9860 9861 if (mLargeIcon != null) { 9862 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 9863 } 9864 if (mColor != Notification.COLOR_DEFAULT) { 9865 carExtensions.putInt(EXTRA_COLOR, mColor); 9866 } 9867 9868 if (mUnreadConversation != null) { 9869 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 9870 carExtensions.putBundle(EXTRA_CONVERSATION, b); 9871 } 9872 9873 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 9874 return builder; 9875 } 9876 9877 /** 9878 * Sets the accent color to use when Android Auto presents the notification. 9879 * 9880 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 9881 * to accent the displayed notification. However, not all colors are acceptable in an 9882 * automotive setting. This method can be used to override the color provided in the 9883 * notification in such a situation. 9884 */ setColor(@olorInt int color)9885 public CarExtender setColor(@ColorInt int color) { 9886 mColor = color; 9887 return this; 9888 } 9889 9890 /** 9891 * Gets the accent color. 9892 * 9893 * @see #setColor 9894 */ 9895 @ColorInt getColor()9896 public int getColor() { 9897 return mColor; 9898 } 9899 9900 /** 9901 * Sets the large icon of the car notification. 9902 * 9903 * If no large icon is set in the extender, Android Auto will display the icon 9904 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 9905 * 9906 * @param largeIcon The large icon to use in the car notification. 9907 * @return This object for method chaining. 9908 */ setLargeIcon(Bitmap largeIcon)9909 public CarExtender setLargeIcon(Bitmap largeIcon) { 9910 mLargeIcon = largeIcon; 9911 return this; 9912 } 9913 9914 /** 9915 * Gets the large icon used in this car notification, or null if no icon has been set. 9916 * 9917 * @return The large icon for the car notification. 9918 * @see CarExtender#setLargeIcon 9919 */ getLargeIcon()9920 public Bitmap getLargeIcon() { 9921 return mLargeIcon; 9922 } 9923 9924 /** 9925 * Sets the unread conversation in a message notification. 9926 * 9927 * @param unreadConversation The unread part of the conversation this notification conveys. 9928 * @return This object for method chaining. 9929 */ setUnreadConversation(UnreadConversation unreadConversation)9930 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 9931 mUnreadConversation = unreadConversation; 9932 return this; 9933 } 9934 9935 /** 9936 * Returns the unread conversation conveyed by this notification. 9937 * @see #setUnreadConversation(UnreadConversation) 9938 */ getUnreadConversation()9939 public UnreadConversation getUnreadConversation() { 9940 return mUnreadConversation; 9941 } 9942 9943 /** 9944 * A class which holds the unread messages from a conversation. 9945 */ 9946 public static class UnreadConversation { 9947 private static final String KEY_AUTHOR = "author"; 9948 private static final String KEY_TEXT = "text"; 9949 private static final String KEY_MESSAGES = "messages"; 9950 private static final String KEY_REMOTE_INPUT = "remote_input"; 9951 private static final String KEY_ON_REPLY = "on_reply"; 9952 private static final String KEY_ON_READ = "on_read"; 9953 private static final String KEY_PARTICIPANTS = "participants"; 9954 private static final String KEY_TIMESTAMP = "timestamp"; 9955 9956 private final String[] mMessages; 9957 private final RemoteInput mRemoteInput; 9958 private final PendingIntent mReplyPendingIntent; 9959 private final PendingIntent mReadPendingIntent; 9960 private final String[] mParticipants; 9961 private final long mLatestTimestamp; 9962 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)9963 UnreadConversation(String[] messages, RemoteInput remoteInput, 9964 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 9965 String[] participants, long latestTimestamp) { 9966 mMessages = messages; 9967 mRemoteInput = remoteInput; 9968 mReadPendingIntent = readPendingIntent; 9969 mReplyPendingIntent = replyPendingIntent; 9970 mParticipants = participants; 9971 mLatestTimestamp = latestTimestamp; 9972 } 9973 9974 /** 9975 * Gets the list of messages conveyed by this notification. 9976 */ getMessages()9977 public String[] getMessages() { 9978 return mMessages; 9979 } 9980 9981 /** 9982 * Gets the remote input that will be used to convey the response to a message list, or 9983 * null if no such remote input exists. 9984 */ getRemoteInput()9985 public RemoteInput getRemoteInput() { 9986 return mRemoteInput; 9987 } 9988 9989 /** 9990 * Gets the pending intent that will be triggered when the user replies to this 9991 * notification. 9992 */ getReplyPendingIntent()9993 public PendingIntent getReplyPendingIntent() { 9994 return mReplyPendingIntent; 9995 } 9996 9997 /** 9998 * Gets the pending intent that Android Auto will send after it reads aloud all messages 9999 * in this object's message list. 10000 */ getReadPendingIntent()10001 public PendingIntent getReadPendingIntent() { 10002 return mReadPendingIntent; 10003 } 10004 10005 /** 10006 * Gets the participants in the conversation. 10007 */ getParticipants()10008 public String[] getParticipants() { 10009 return mParticipants; 10010 } 10011 10012 /** 10013 * Gets the firs participant in the conversation. 10014 */ getParticipant()10015 public String getParticipant() { 10016 return mParticipants.length > 0 ? mParticipants[0] : null; 10017 } 10018 10019 /** 10020 * Gets the timestamp of the conversation. 10021 */ getLatestTimestamp()10022 public long getLatestTimestamp() { 10023 return mLatestTimestamp; 10024 } 10025 getBundleForUnreadConversation()10026 Bundle getBundleForUnreadConversation() { 10027 Bundle b = new Bundle(); 10028 String author = null; 10029 if (mParticipants != null && mParticipants.length > 1) { 10030 author = mParticipants[0]; 10031 } 10032 Parcelable[] messages = new Parcelable[mMessages.length]; 10033 for (int i = 0; i < messages.length; i++) { 10034 Bundle m = new Bundle(); 10035 m.putString(KEY_TEXT, mMessages[i]); 10036 m.putString(KEY_AUTHOR, author); 10037 messages[i] = m; 10038 } 10039 b.putParcelableArray(KEY_MESSAGES, messages); 10040 if (mRemoteInput != null) { 10041 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 10042 } 10043 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 10044 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 10045 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 10046 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 10047 return b; 10048 } 10049 getUnreadConversationFromBundle(Bundle b)10050 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 10051 if (b == null) { 10052 return null; 10053 } 10054 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 10055 String[] messages = null; 10056 if (parcelableMessages != null) { 10057 String[] tmp = new String[parcelableMessages.length]; 10058 boolean success = true; 10059 for (int i = 0; i < tmp.length; i++) { 10060 if (!(parcelableMessages[i] instanceof Bundle)) { 10061 success = false; 10062 break; 10063 } 10064 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 10065 if (tmp[i] == null) { 10066 success = false; 10067 break; 10068 } 10069 } 10070 if (success) { 10071 messages = tmp; 10072 } else { 10073 return null; 10074 } 10075 } 10076 10077 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 10078 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 10079 10080 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 10081 10082 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 10083 if (participants == null || participants.length != 1) { 10084 return null; 10085 } 10086 10087 return new UnreadConversation(messages, 10088 remoteInput, 10089 onReply, 10090 onRead, 10091 participants, b.getLong(KEY_TIMESTAMP)); 10092 } 10093 }; 10094 10095 /** 10096 * Builder class for {@link CarExtender.UnreadConversation} objects. 10097 */ 10098 public static class Builder { 10099 private final List<String> mMessages = new ArrayList<String>(); 10100 private final String mParticipant; 10101 private RemoteInput mRemoteInput; 10102 private PendingIntent mReadPendingIntent; 10103 private PendingIntent mReplyPendingIntent; 10104 private long mLatestTimestamp; 10105 10106 /** 10107 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 10108 * 10109 * @param name The name of the other participant in the conversation. 10110 */ Builder(String name)10111 public Builder(String name) { 10112 mParticipant = name; 10113 } 10114 10115 /** 10116 * Appends a new unread message to the list of messages for this conversation. 10117 * 10118 * The messages should be added from oldest to newest. 10119 * 10120 * @param message The text of the new unread message. 10121 * @return This object for method chaining. 10122 */ addMessage(String message)10123 public Builder addMessage(String message) { 10124 mMessages.add(message); 10125 return this; 10126 } 10127 10128 /** 10129 * Sets the pending intent and remote input which will convey the reply to this 10130 * notification. 10131 * 10132 * @param pendingIntent The pending intent which will be triggered on a reply. 10133 * @param remoteInput The remote input parcelable which will carry the reply. 10134 * @return This object for method chaining. 10135 * 10136 * @see CarExtender.UnreadConversation#getRemoteInput 10137 * @see CarExtender.UnreadConversation#getReplyPendingIntent 10138 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)10139 public Builder setReplyAction( 10140 PendingIntent pendingIntent, RemoteInput remoteInput) { 10141 mRemoteInput = remoteInput; 10142 mReplyPendingIntent = pendingIntent; 10143 10144 return this; 10145 } 10146 10147 /** 10148 * Sets the pending intent that will be sent once the messages in this notification 10149 * are read. 10150 * 10151 * @param pendingIntent The pending intent to use. 10152 * @return This object for method chaining. 10153 */ setReadPendingIntent(PendingIntent pendingIntent)10154 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 10155 mReadPendingIntent = pendingIntent; 10156 return this; 10157 } 10158 10159 /** 10160 * Sets the timestamp of the most recent message in an unread conversation. 10161 * 10162 * If a messaging notification has been posted by your application and has not 10163 * yet been cancelled, posting a later notification with the same id and tag 10164 * but without a newer timestamp may result in Android Auto not displaying a 10165 * heads up notification for the later notification. 10166 * 10167 * @param timestamp The timestamp of the most recent message in the conversation. 10168 * @return This object for method chaining. 10169 */ setLatestTimestamp(long timestamp)10170 public Builder setLatestTimestamp(long timestamp) { 10171 mLatestTimestamp = timestamp; 10172 return this; 10173 } 10174 10175 /** 10176 * Builds a new unread conversation object. 10177 * 10178 * @return The new unread conversation object. 10179 */ build()10180 public UnreadConversation build() { 10181 String[] messages = mMessages.toArray(new String[mMessages.size()]); 10182 String[] participants = { mParticipant }; 10183 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 10184 mReadPendingIntent, participants, mLatestTimestamp); 10185 } 10186 } 10187 } 10188 10189 /** 10190 * <p>Helper class to add Android TV extensions to notifications. To create a notification 10191 * with a TV extension: 10192 * 10193 * <ol> 10194 * <li>Create an {@link Notification.Builder}, setting any desired properties. 10195 * <li>Create a {@link TvExtender}. 10196 * <li>Set TV-specific properties using the {@code set} methods of 10197 * {@link TvExtender}. 10198 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 10199 * to apply the extension to a notification. 10200 * </ol> 10201 * 10202 * <pre class="prettyprint"> 10203 * Notification notification = new Notification.Builder(context) 10204 * ... 10205 * .extend(new TvExtender() 10206 * .set*(...)) 10207 * .build(); 10208 * </pre> 10209 * 10210 * <p>TV extensions can be accessed on an existing notification by using the 10211 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 10212 * to access values. 10213 * 10214 * @hide 10215 */ 10216 @SystemApi 10217 public static final class TvExtender implements Extender { 10218 private static final String TAG = "TvExtender"; 10219 10220 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 10221 private static final String EXTRA_FLAGS = "flags"; 10222 private static final String EXTRA_CONTENT_INTENT = "content_intent"; 10223 private static final String EXTRA_DELETE_INTENT = "delete_intent"; 10224 private static final String EXTRA_CHANNEL_ID = "channel_id"; 10225 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 10226 10227 // Flags bitwise-ored to mFlags 10228 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 10229 10230 private int mFlags; 10231 private String mChannelId; 10232 private PendingIntent mContentIntent; 10233 private PendingIntent mDeleteIntent; 10234 private boolean mSuppressShowOverApps; 10235 10236 /** 10237 * Create a {@link TvExtender} with default options. 10238 */ TvExtender()10239 public TvExtender() { 10240 mFlags = FLAG_AVAILABLE_ON_TV; 10241 } 10242 10243 /** 10244 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 10245 * 10246 * @param notif The notification from which to copy options. 10247 */ TvExtender(Notification notif)10248 public TvExtender(Notification notif) { 10249 Bundle bundle = notif.extras == null ? 10250 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 10251 if (bundle != null) { 10252 mFlags = bundle.getInt(EXTRA_FLAGS); 10253 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 10254 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 10255 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT); 10256 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT); 10257 } 10258 } 10259 10260 /** 10261 * Apply a TV extension to a notification that is being built. This is typically called by 10262 * the {@link Notification.Builder#extend(Notification.Extender)} 10263 * method of {@link Notification.Builder}. 10264 */ 10265 @Override extend(Notification.Builder builder)10266 public Notification.Builder extend(Notification.Builder builder) { 10267 Bundle bundle = new Bundle(); 10268 10269 bundle.putInt(EXTRA_FLAGS, mFlags); 10270 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 10271 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 10272 if (mContentIntent != null) { 10273 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 10274 } 10275 10276 if (mDeleteIntent != null) { 10277 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 10278 } 10279 10280 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 10281 return builder; 10282 } 10283 10284 /** 10285 * Returns true if this notification should be shown on TV. This method return true 10286 * if the notification was extended with a TvExtender. 10287 */ isAvailableOnTv()10288 public boolean isAvailableOnTv() { 10289 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 10290 } 10291 10292 /** 10293 * Specifies the channel the notification should be delivered on when shown on TV. 10294 * It can be different from the channel that the notification is delivered to when 10295 * posting on a non-TV device. 10296 */ setChannel(String channelId)10297 public TvExtender setChannel(String channelId) { 10298 mChannelId = channelId; 10299 return this; 10300 } 10301 10302 /** 10303 * Specifies the channel the notification should be delivered on when shown on TV. 10304 * It can be different from the channel that the notification is delivered to when 10305 * posting on a non-TV device. 10306 */ setChannelId(String channelId)10307 public TvExtender setChannelId(String channelId) { 10308 mChannelId = channelId; 10309 return this; 10310 } 10311 10312 /** @removed */ 10313 @Deprecated getChannel()10314 public String getChannel() { 10315 return mChannelId; 10316 } 10317 10318 /** 10319 * Returns the id of the channel this notification posts to on TV. 10320 */ getChannelId()10321 public String getChannelId() { 10322 return mChannelId; 10323 } 10324 10325 /** 10326 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 10327 * If provided, it is used instead of the content intent specified 10328 * at the level of Notification. 10329 */ setContentIntent(PendingIntent intent)10330 public TvExtender setContentIntent(PendingIntent intent) { 10331 mContentIntent = intent; 10332 return this; 10333 } 10334 10335 /** 10336 * Returns the TV-specific content intent. If this method returns null, the 10337 * main content intent on the notification should be used. 10338 * 10339 * @see {@link Notification#contentIntent} 10340 */ getContentIntent()10341 public PendingIntent getContentIntent() { 10342 return mContentIntent; 10343 } 10344 10345 /** 10346 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 10347 * by the user on TV. If provided, it is used instead of the delete intent specified 10348 * at the level of Notification. 10349 */ setDeleteIntent(PendingIntent intent)10350 public TvExtender setDeleteIntent(PendingIntent intent) { 10351 mDeleteIntent = intent; 10352 return this; 10353 } 10354 10355 /** 10356 * Returns the TV-specific delete intent. If this method returns null, the 10357 * main delete intent on the notification should be used. 10358 * 10359 * @see {@link Notification#deleteIntent} 10360 */ getDeleteIntent()10361 public PendingIntent getDeleteIntent() { 10362 return mDeleteIntent; 10363 } 10364 10365 /** 10366 * Specifies whether this notification should suppress showing a message over top of apps 10367 * outside of the launcher. 10368 */ setSuppressShowOverApps(boolean suppress)10369 public TvExtender setSuppressShowOverApps(boolean suppress) { 10370 mSuppressShowOverApps = suppress; 10371 return this; 10372 } 10373 10374 /** 10375 * Returns true if this notification should not show messages over top of apps 10376 * outside of the launcher. 10377 */ getSuppressShowOverApps()10378 public boolean getSuppressShowOverApps() { 10379 return mSuppressShowOverApps; 10380 } 10381 } 10382 10383 /** 10384 * Get an array of Notification objects from a parcelable array bundle field. 10385 * Update the bundle to have a typed array so fetches in the future don't need 10386 * to do an array copy. 10387 */ getNotificationArrayFromBundle(Bundle bundle, String key)10388 private static Notification[] getNotificationArrayFromBundle(Bundle bundle, String key) { 10389 Parcelable[] array = bundle.getParcelableArray(key); 10390 if (array instanceof Notification[] || array == null) { 10391 return (Notification[]) array; 10392 } 10393 Notification[] typedArray = Arrays.copyOf(array, array.length, 10394 Notification[].class); 10395 bundle.putParcelableArray(key, typedArray); 10396 return typedArray; 10397 } 10398 10399 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)10400 public BuilderRemoteViews(Parcel parcel) { 10401 super(parcel); 10402 } 10403 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)10404 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 10405 super(appInfo, layoutId); 10406 } 10407 10408 @Override clone()10409 public BuilderRemoteViews clone() { 10410 Parcel p = Parcel.obtain(); 10411 writeToParcel(p, 0); 10412 p.setDataPosition(0); 10413 BuilderRemoteViews brv = new BuilderRemoteViews(p); 10414 p.recycle(); 10415 return brv; 10416 } 10417 } 10418 10419 /** 10420 * A result object where information about the template that was created is saved. 10421 */ 10422 private static class TemplateBindResult { 10423 int mIconMarginEnd; 10424 boolean mRightIconContainerVisible; 10425 10426 /** 10427 * Get the margin end that needs to be added to any fields that may overlap 10428 * with the right actions. 10429 */ getIconMarginEnd()10430 public int getIconMarginEnd() { 10431 return mIconMarginEnd; 10432 } 10433 10434 /** 10435 * Is the icon container visible on the right size because of the reply button or the 10436 * right icon. 10437 */ isRightIconContainerVisible()10438 public boolean isRightIconContainerVisible() { 10439 return mRightIconContainerVisible; 10440 } 10441 setIconMarginEnd(int iconMarginEnd)10442 public void setIconMarginEnd(int iconMarginEnd) { 10443 this.mIconMarginEnd = iconMarginEnd; 10444 } 10445 setRightIconContainerVisible(boolean iconContainerVisible)10446 public void setRightIconContainerVisible(boolean iconContainerVisible) { 10447 mRightIconContainerVisible = iconContainerVisible; 10448 } 10449 } 10450 10451 private static class StandardTemplateParams { 10452 boolean hasProgress = true; 10453 CharSequence title; 10454 CharSequence text; 10455 CharSequence headerTextSecondary; 10456 CharSequence summaryText; 10457 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 10458 boolean hideLargeIcon; 10459 boolean hideReplyIcon; 10460 boolean allowColorization = true; 10461 boolean forceDefaultColor = false; 10462 reset()10463 final StandardTemplateParams reset() { 10464 hasProgress = true; 10465 title = null; 10466 text = null; 10467 summaryText = null; 10468 headerTextSecondary = null; 10469 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 10470 allowColorization = true; 10471 forceDefaultColor = false; 10472 return this; 10473 } 10474 hasProgress(boolean hasProgress)10475 final StandardTemplateParams hasProgress(boolean hasProgress) { 10476 this.hasProgress = hasProgress; 10477 return this; 10478 } 10479 title(CharSequence title)10480 final StandardTemplateParams title(CharSequence title) { 10481 this.title = title; 10482 return this; 10483 } 10484 text(CharSequence text)10485 final StandardTemplateParams text(CharSequence text) { 10486 this.text = text; 10487 return this; 10488 } 10489 summaryText(CharSequence text)10490 final StandardTemplateParams summaryText(CharSequence text) { 10491 this.summaryText = text; 10492 return this; 10493 } 10494 headerTextSecondary(CharSequence text)10495 final StandardTemplateParams headerTextSecondary(CharSequence text) { 10496 this.headerTextSecondary = text; 10497 return this; 10498 } 10499 hideLargeIcon(boolean hideLargeIcon)10500 final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) { 10501 this.hideLargeIcon = hideLargeIcon; 10502 return this; 10503 } 10504 hideReplyIcon(boolean hideReplyIcon)10505 final StandardTemplateParams hideReplyIcon(boolean hideReplyIcon) { 10506 this.hideReplyIcon = hideReplyIcon; 10507 return this; 10508 } 10509 disallowColorization()10510 final StandardTemplateParams disallowColorization() { 10511 this.allowColorization = false; 10512 return this; 10513 } 10514 forceDefaultColor()10515 final StandardTemplateParams forceDefaultColor() { 10516 this.forceDefaultColor = true; 10517 return this; 10518 } 10519 fillTextsFrom(Builder b)10520 final StandardTemplateParams fillTextsFrom(Builder b) { 10521 Bundle extras = b.mN.extras; 10522 this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 10523 this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 10524 this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT); 10525 return this; 10526 } 10527 10528 /** 10529 * Set the maximum lines of remote input history lines allowed. 10530 * @param maxRemoteInputHistory The number of lines. 10531 * @return The builder for method chaining. 10532 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)10533 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 10534 this.maxRemoteInputHistory = maxRemoteInputHistory; 10535 return this; 10536 } 10537 } 10538 } 10539