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_URI; 21 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; 22 23 import static java.util.Objects.requireNonNull; 24 25 import android.annotation.ColorInt; 26 import android.annotation.ColorRes; 27 import android.annotation.DimenRes; 28 import android.annotation.Dimension; 29 import android.annotation.DrawableRes; 30 import android.annotation.IdRes; 31 import android.annotation.IntDef; 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.annotation.RequiresPermission; 35 import android.annotation.SdkConstant; 36 import android.annotation.SdkConstant.SdkConstantType; 37 import android.annotation.StringRes; 38 import android.annotation.StyleableRes; 39 import android.annotation.SuppressLint; 40 import android.annotation.SystemApi; 41 import android.annotation.TestApi; 42 import android.compat.annotation.UnsupportedAppUsage; 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.LocusId; 46 import android.content.pm.ApplicationInfo; 47 import android.content.pm.PackageManager; 48 import android.content.pm.PackageManager.NameNotFoundException; 49 import android.content.pm.ShortcutInfo; 50 import android.content.res.ColorStateList; 51 import android.content.res.Configuration; 52 import android.content.res.Resources; 53 import android.content.res.TypedArray; 54 import android.graphics.Bitmap; 55 import android.graphics.Canvas; 56 import android.graphics.Color; 57 import android.graphics.PorterDuff; 58 import android.graphics.drawable.Drawable; 59 import android.graphics.drawable.Icon; 60 import android.media.AudioAttributes; 61 import android.media.AudioManager; 62 import android.media.PlayerBase; 63 import android.media.session.MediaSession; 64 import android.net.Uri; 65 import android.os.BadParcelableException; 66 import android.os.Build; 67 import android.os.Bundle; 68 import android.os.IBinder; 69 import android.os.Parcel; 70 import android.os.Parcelable; 71 import android.os.SystemClock; 72 import android.os.SystemProperties; 73 import android.os.UserHandle; 74 import android.provider.Settings; 75 import android.text.BidiFormatter; 76 import android.text.SpannableStringBuilder; 77 import android.text.Spanned; 78 import android.text.TextUtils; 79 import android.text.style.AbsoluteSizeSpan; 80 import android.text.style.CharacterStyle; 81 import android.text.style.ForegroundColorSpan; 82 import android.text.style.RelativeSizeSpan; 83 import android.text.style.TextAppearanceSpan; 84 import android.util.ArraySet; 85 import android.util.Log; 86 import android.util.Pair; 87 import android.util.SparseArray; 88 import android.util.TypedValue; 89 import android.util.proto.ProtoOutputStream; 90 import android.view.ContextThemeWrapper; 91 import android.view.Gravity; 92 import android.view.View; 93 import android.view.contentcapture.ContentCaptureContext; 94 import android.widget.ProgressBar; 95 import android.widget.RemoteViews; 96 97 import com.android.internal.R; 98 import com.android.internal.annotations.VisibleForTesting; 99 import com.android.internal.graphics.ColorUtils; 100 import com.android.internal.util.ArrayUtils; 101 import com.android.internal.util.ContrastColorUtil; 102 103 import java.lang.annotation.Retention; 104 import java.lang.annotation.RetentionPolicy; 105 import java.lang.reflect.Array; 106 import java.lang.reflect.Constructor; 107 import java.util.ArrayList; 108 import java.util.Arrays; 109 import java.util.Collections; 110 import java.util.List; 111 import java.util.Objects; 112 import java.util.Set; 113 import java.util.function.Consumer; 114 115 /** 116 * A class that represents how a persistent notification is to be presented to 117 * the user using the {@link android.app.NotificationManager}. 118 * 119 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 120 * easier to construct Notifications.</p> 121 * 122 * <div class="special reference"> 123 * <h3>Developer Guides</h3> 124 * <p>For a guide to creating notifications, read the 125 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 126 * developer guide.</p> 127 * </div> 128 */ 129 public class Notification implements Parcelable 130 { 131 private static final String TAG = "Notification"; 132 133 /** 134 * @hide 135 */ 136 @Retention(RetentionPolicy.SOURCE) 137 @IntDef({ 138 FOREGROUND_SERVICE_DEFAULT, 139 FOREGROUND_SERVICE_IMMEDIATE, 140 FOREGROUND_SERVICE_DEFERRED 141 }) 142 public @interface ServiceNotificationPolicy {}; 143 144 /** 145 * If the Notification associated with starting a foreground service has been 146 * built using setForegroundServiceBehavior() with this behavior, display of 147 * the notification will usually be suppressed for a short time to avoid visual 148 * disturbances to the user. 149 * @see Notification.Builder#setForegroundServiceBehavior(int) 150 * @see #FOREGROUND_SERVICE_IMMEDIATE 151 * @see #FOREGROUND_SERVICE_DEFERRED 152 */ 153 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0; 154 155 /** 156 * If the Notification associated with starting a foreground service has been 157 * built using setForegroundServiceBehavior() with this behavior, display of 158 * the notification will be immediate even if the default behavior would be 159 * to defer visibility for a short time. 160 * @see Notification.Builder#setForegroundServiceBehavior(int) 161 * @see #FOREGROUND_SERVICE_DEFAULT 162 * @see #FOREGROUND_SERVICE_DEFERRED 163 */ 164 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1; 165 166 /** 167 * If the Notification associated with starting a foreground service has been 168 * built using setForegroundServiceBehavior() with this behavior, display of 169 * the notification will usually be suppressed for a short time to avoid visual 170 * disturbances to the user. 171 * @see Notification.Builder#setForegroundServiceBehavior(int) 172 * @see #FOREGROUND_SERVICE_DEFAULT 173 * @see #FOREGROUND_SERVICE_IMMEDIATE 174 */ 175 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2; 176 177 @ServiceNotificationPolicy 178 private int mFgsDeferBehavior; 179 180 /** 181 * An activity that provides a user interface for adjusting notification preferences for its 182 * containing application. 183 */ 184 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 185 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES 186 = "android.intent.category.NOTIFICATION_PREFERENCES"; 187 188 /** 189 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 190 * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down 191 * what settings should be shown in the target app. 192 */ 193 public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; 194 195 /** 196 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 197 * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down 198 * what settings should be shown in the target app. 199 */ 200 public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; 201 202 /** 203 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 204 * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)} 205 * that can be used to narrow down what settings should be shown in the target app. 206 */ 207 public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; 208 209 /** 210 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 211 * contain the id provided to {@link NotificationManager#notify(String, int, Notification)} 212 * that can be used to narrow down what settings should be shown in the target app. 213 */ 214 public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; 215 216 /** 217 * Use all default values (where applicable). 218 */ 219 public static final int DEFAULT_ALL = ~0; 220 221 /** 222 * Use the default notification sound. This will ignore any given 223 * {@link #sound}. 224 * 225 * <p> 226 * A notification that is noisy is more likely to be presented as a heads-up notification. 227 * </p> 228 * 229 * @see #defaults 230 */ 231 232 public static final int DEFAULT_SOUND = 1; 233 234 /** 235 * Use the default notification vibrate. This will ignore any given 236 * {@link #vibrate}. Using phone vibration requires the 237 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 238 * 239 * <p> 240 * A notification that vibrates is more likely to be presented as a heads-up notification. 241 * </p> 242 * 243 * @see #defaults 244 */ 245 246 public static final int DEFAULT_VIBRATE = 2; 247 248 /** 249 * Use the default notification lights. This will ignore the 250 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 251 * {@link #ledOnMS}. 252 * 253 * @see #defaults 254 */ 255 256 public static final int DEFAULT_LIGHTS = 4; 257 258 /** 259 * Maximum length of CharSequences accepted by Builder and friends. 260 * 261 * <p> 262 * Avoids spamming the system with overly large strings such as full e-mails. 263 */ 264 private static final int MAX_CHARSEQUENCE_LENGTH = 1024; 265 266 /** 267 * Maximum entries of reply text that are accepted by Builder and friends. 268 */ 269 private static final int MAX_REPLY_HISTORY = 5; 270 271 /** 272 * Maximum aspect ratio of the large icon. 16:9 273 */ 274 private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f; 275 276 /** 277 * Maximum number of (generic) action buttons in a notification (contextual action buttons are 278 * handled separately). 279 * @hide 280 */ 281 public static final int MAX_ACTION_BUTTONS = 3; 282 283 /** 284 * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, 285 * we're adding the draft as a String extra to the {@link #contentIntent} using this key. 286 * 287 * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually 288 * sends messages.</p> 289 */ 290 public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; 291 292 /** 293 * A timestamp related to this notification, in milliseconds since the epoch. 294 * 295 * Default value: {@link System#currentTimeMillis() Now}. 296 * 297 * Choose a timestamp that will be most relevant to the user. For most finite events, this 298 * corresponds to the time the event happened (or will happen, in the case of events that have 299 * yet to occur but about which the user is being informed). Indefinite events should be 300 * timestamped according to when the activity began. 301 * 302 * Some examples: 303 * 304 * <ul> 305 * <li>Notification of a new chat message should be stamped when the message was received.</li> 306 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 307 * <li>Notification of a completed file download should be stamped when the download finished.</li> 308 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 309 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 310 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 311 * </ul> 312 * 313 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown 314 * anymore by default and must be opted into by using 315 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 316 */ 317 public long when; 318 319 /** 320 * The creation time of the notification 321 */ 322 private long creationTime; 323 324 /** 325 * The resource id of a drawable to use as the icon in the status bar. 326 * 327 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead. 328 */ 329 @Deprecated 330 @DrawableRes 331 public int icon; 332 333 /** 334 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 335 * leave it at its default value of 0. 336 * 337 * @see android.widget.ImageView#setImageLevel 338 * @see android.graphics.drawable.Drawable#setLevel 339 */ 340 public int iconLevel; 341 342 /** 343 * The number of events that this notification represents. For example, in a new mail 344 * notification, this could be the number of unread messages. 345 * 346 * The system may or may not use this field to modify the appearance of the notification. 347 * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a 348 * badge icon in Launchers that support badging. 349 */ 350 public int number = 0; 351 352 /** 353 * The intent to execute when the expanded status entry is clicked. If 354 * this is an activity, it must include the 355 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 356 * that you take care of task management as described in the 357 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 358 * Stack</a> document. In particular, make sure to read the notification section 359 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling 360 * Notifications</a> for the correct ways to launch an application from a 361 * notification. 362 */ 363 public PendingIntent contentIntent; 364 365 /** 366 * The intent to execute when the notification is explicitly dismissed by the user, either with 367 * the "Clear All" button or by swiping it away individually. 368 * 369 * This probably shouldn't be launching an activity since several of those will be sent 370 * at the same time. 371 */ 372 public PendingIntent deleteIntent; 373 374 /** 375 * An intent to launch instead of posting the notification to the status bar. 376 * 377 * <p> 378 * The system UI may choose to display a heads-up notification, instead of 379 * launching this intent, while the user is using the device. 380 * </p> 381 * 382 * @see Notification.Builder#setFullScreenIntent 383 */ 384 public PendingIntent fullScreenIntent; 385 386 /** 387 * Text that summarizes this notification for accessibility services. 388 * 389 * As of the L release, this text is no longer shown on screen, but it is still useful to 390 * accessibility services (where it serves as an audible announcement of the notification's 391 * appearance). 392 * 393 * @see #tickerView 394 */ 395 public CharSequence tickerText; 396 397 /** 398 * Formerly, a view showing the {@link #tickerText}. 399 * 400 * No longer displayed in the status bar as of API 21. 401 */ 402 @Deprecated 403 public RemoteViews tickerView; 404 405 /** 406 * The view that will represent this notification in the notification list (which is pulled 407 * down from the status bar). 408 * 409 * As of N, this field may be null. The notification view is determined by the inputs 410 * to {@link Notification.Builder}; a custom RemoteViews can optionally be 411 * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. 412 */ 413 @Deprecated 414 public RemoteViews contentView; 415 416 /** 417 * A large-format version of {@link #contentView}, giving the Notification an 418 * opportunity to show more detail. The system UI may choose to show this 419 * instead of the normal content view at its discretion. 420 * 421 * As of N, this field may be null. The expanded notification view is determined by the 422 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 423 * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. 424 */ 425 @Deprecated 426 public RemoteViews bigContentView; 427 428 429 /** 430 * A medium-format version of {@link #contentView}, providing the Notification an 431 * opportunity to add action buttons to contentView. At its discretion, the system UI may 432 * choose to show this as a heads-up notification, which will pop up so the user can see 433 * it without leaving their current activity. 434 * 435 * As of N, this field may be null. The heads-up notification view is determined by the 436 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 437 * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. 438 */ 439 @Deprecated 440 public RemoteViews headsUpContentView; 441 442 private boolean mUsesStandardHeader; 443 444 private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>(); 445 static { 446 STANDARD_LAYOUTS.add(R.layout.notification_template_material_base); 447 STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base); 448 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base); 449 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture); 450 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); 451 STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); 452 STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); 453 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging); 454 STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation); 455 STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); 456 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); 457 STANDARD_LAYOUTS.add(R.layout.notification_template_material_call); 458 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call); 459 STANDARD_LAYOUTS.add(R.layout.notification_template_header); 460 } 461 462 /** 463 * A large bitmap to be shown in the notification content area. 464 * 465 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead. 466 */ 467 @Deprecated 468 public Bitmap largeIcon; 469 470 /** 471 * The sound to play. 472 * 473 * <p> 474 * A notification that is noisy is more likely to be presented as a heads-up notification. 475 * </p> 476 * 477 * <p> 478 * To play the default notification sound, see {@link #defaults}. 479 * </p> 480 * @deprecated use {@link NotificationChannel#getSound()}. 481 */ 482 @Deprecated 483 public Uri sound; 484 485 /** 486 * Use this constant as the value for audioStreamType to request that 487 * the default stream type for notifications be used. Currently the 488 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 489 * 490 * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead. 491 */ 492 @Deprecated 493 public static final int STREAM_DEFAULT = -1; 494 495 /** 496 * The audio stream type to use when playing the sound. 497 * Should be one of the STREAM_ constants from 498 * {@link android.media.AudioManager}. 499 * 500 * @deprecated Use {@link #audioAttributes} instead. 501 */ 502 @Deprecated 503 public int audioStreamType = STREAM_DEFAULT; 504 505 /** 506 * The default value of {@link #audioAttributes}. 507 */ 508 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() 509 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 510 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 511 .build(); 512 513 /** 514 * The {@link AudioAttributes audio attributes} to use when playing the sound. 515 * 516 * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead. 517 */ 518 @Deprecated 519 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 520 521 /** 522 * The pattern with which to vibrate. 523 * 524 * <p> 525 * To vibrate the default pattern, see {@link #defaults}. 526 * </p> 527 * 528 * @see android.os.Vibrator#vibrate(long[],int) 529 * @deprecated use {@link NotificationChannel#getVibrationPattern()}. 530 */ 531 @Deprecated 532 public long[] vibrate; 533 534 /** 535 * The color of the led. The hardware will do its best approximation. 536 * 537 * @see #FLAG_SHOW_LIGHTS 538 * @see #flags 539 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 540 */ 541 @ColorInt 542 @Deprecated 543 public int ledARGB; 544 545 /** 546 * The number of milliseconds for the LED to be on while it's flashing. 547 * The hardware will do its best approximation. 548 * 549 * @see #FLAG_SHOW_LIGHTS 550 * @see #flags 551 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 552 */ 553 @Deprecated 554 public int ledOnMS; 555 556 /** 557 * The number of milliseconds for the LED to be off while it's flashing. 558 * The hardware will do its best approximation. 559 * 560 * @see #FLAG_SHOW_LIGHTS 561 * @see #flags 562 * 563 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 564 */ 565 @Deprecated 566 public int ledOffMS; 567 568 /** 569 * Specifies which values should be taken from the defaults. 570 * <p> 571 * To set, OR the desired from {@link #DEFAULT_SOUND}, 572 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 573 * values, use {@link #DEFAULT_ALL}. 574 * </p> 575 * 576 * @deprecated use {@link NotificationChannel#getSound()} and 577 * {@link NotificationChannel#shouldShowLights()} and 578 * {@link NotificationChannel#shouldVibrate()}. 579 */ 580 @Deprecated 581 public int defaults; 582 583 /** 584 * Bit to be bitwise-ored into the {@link #flags} field that should be 585 * set if you want the LED on for this notification. 586 * <ul> 587 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 588 * or 0 for both ledOnMS and ledOffMS.</li> 589 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 590 * <li>To flash the LED, pass the number of milliseconds that it should 591 * be on and off to ledOnMS and ledOffMS.</li> 592 * </ul> 593 * <p> 594 * Since hardware varies, you are not guaranteed that any of the values 595 * you pass are honored exactly. Use the system defaults if possible 596 * because they will be set to values that work on any given hardware. 597 * <p> 598 * The alpha channel must be set for forward compatibility. 599 * 600 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 601 */ 602 @Deprecated 603 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 604 605 /** 606 * Bit to be bitwise-ored into the {@link #flags} field that should be 607 * set if this notification is in reference to something that is ongoing, 608 * like a phone call. It should not be set if this notification is in 609 * reference to something that happened at a particular point in time, 610 * like a missed phone call. 611 */ 612 public static final int FLAG_ONGOING_EVENT = 0x00000002; 613 614 /** 615 * Bit to be bitwise-ored into the {@link #flags} field that if set, 616 * the audio will be repeated until the notification is 617 * cancelled or the notification window is opened. 618 */ 619 public static final int FLAG_INSISTENT = 0x00000004; 620 621 /** 622 * Bit to be bitwise-ored into the {@link #flags} field that should be 623 * set if you would only like the sound, vibrate and ticker to be played 624 * if the notification was not already showing. 625 */ 626 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 627 628 /** 629 * Bit to be bitwise-ored into the {@link #flags} field that should be 630 * set if the notification should be canceled when it is clicked by the 631 * user. 632 */ 633 public static final int FLAG_AUTO_CANCEL = 0x00000010; 634 635 /** 636 * Bit to be bitwise-ored into the {@link #flags} field that should be 637 * set if the notification should not be canceled when the user clicks 638 * the Clear all button. 639 */ 640 public static final int FLAG_NO_CLEAR = 0x00000020; 641 642 /** 643 * Bit to be bitwise-ored into the {@link #flags} field that should be 644 * set if this notification represents a currently running service. This 645 * will normally be set for you by {@link Service#startForeground}. 646 */ 647 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 648 649 /** 650 * Obsolete flag indicating high-priority notifications; use the priority field instead. 651 * 652 * @deprecated Use {@link #priority} with a positive value. 653 */ 654 @Deprecated 655 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 656 657 /** 658 * Bit to be bitswise-ored into the {@link #flags} field that should be 659 * set if this notification is relevant to the current device only 660 * and it is not recommended that it bridge to other devices. 661 */ 662 public static final int FLAG_LOCAL_ONLY = 0x00000100; 663 664 /** 665 * Bit to be bitswise-ored into the {@link #flags} field that should be 666 * set if this notification is the group summary for a group of notifications. 667 * Grouped notifications may display in a cluster or stack on devices which 668 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 669 */ 670 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 671 672 /** 673 * Bit to be bitswise-ored into the {@link #flags} field that should be 674 * set if this notification is the group summary for an auto-group of notifications. 675 * 676 * @hide 677 */ 678 @SystemApi 679 public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400; 680 681 /** 682 * @hide 683 */ 684 public static final int FLAG_CAN_COLORIZE = 0x00000800; 685 686 /** 687 * Bit to be bitswised-ored into the {@link #flags} field that should be 688 * set by the system if this notification is showing as a bubble. 689 * 690 * Applications cannot set this flag directly; they should instead call 691 * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to 692 * request that a notification be displayed as a bubble, and then check 693 * this flag to see whether that request was honored by the system. 694 */ 695 public static final int FLAG_BUBBLE = 0x00001000; 696 697 private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( 698 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 699 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 700 MessagingStyle.class, CallStyle.class); 701 702 /** @hide */ 703 @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, 704 FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, 705 FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, 706 FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE}) 707 @Retention(RetentionPolicy.SOURCE) 708 public @interface NotificationFlags{}; 709 710 public int flags; 711 712 /** @hide */ 713 @IntDef(prefix = { "PRIORITY_" }, value = { 714 PRIORITY_DEFAULT, 715 PRIORITY_LOW, 716 PRIORITY_MIN, 717 PRIORITY_HIGH, 718 PRIORITY_MAX 719 }) 720 @Retention(RetentionPolicy.SOURCE) 721 public @interface Priority {} 722 723 /** 724 * Default notification {@link #priority}. If your application does not prioritize its own 725 * notifications, use this value for all notifications. 726 * 727 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. 728 */ 729 @Deprecated 730 public static final int PRIORITY_DEFAULT = 0; 731 732 /** 733 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 734 * items smaller, or at a different position in the list, compared with your app's 735 * {@link #PRIORITY_DEFAULT} items. 736 * 737 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. 738 */ 739 @Deprecated 740 public static final int PRIORITY_LOW = -1; 741 742 /** 743 * Lowest {@link #priority}; these items might not be shown to the user except under special 744 * circumstances, such as detailed notification logs. 745 * 746 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. 747 */ 748 @Deprecated 749 public static final int PRIORITY_MIN = -2; 750 751 /** 752 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 753 * show these items larger, or at a different position in notification lists, compared with 754 * your app's {@link #PRIORITY_DEFAULT} items. 755 * 756 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 757 */ 758 @Deprecated 759 public static final int PRIORITY_HIGH = 1; 760 761 /** 762 * Highest {@link #priority}, for your application's most important items that require the 763 * user's prompt attention or input. 764 * 765 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 766 */ 767 @Deprecated 768 public static final int PRIORITY_MAX = 2; 769 770 /** 771 * Relative priority for this notification. 772 * 773 * Priority is an indication of how much of the user's valuable attention should be consumed by 774 * this notification. Low-priority notifications may be hidden from the user in certain 775 * situations, while the user might be interrupted for a higher-priority notification. The 776 * system will make a determination about how to interpret this priority when presenting 777 * the notification. 778 * 779 * <p> 780 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 781 * as a heads-up notification. 782 * </p> 783 * 784 * @deprecated use {@link NotificationChannel#getImportance()} instead. 785 */ 786 @Priority 787 @Deprecated 788 public int priority; 789 790 /** 791 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 792 * to be applied by the standard Style templates when presenting this notification. 793 * 794 * The current template design constructs a colorful header image by overlaying the 795 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 796 * ignored. 797 */ 798 @ColorInt 799 public int color = COLOR_DEFAULT; 800 801 /** 802 * Special value of {@link #color} telling the system not to decorate this notification with 803 * any special color but instead use default colors when presenting this notification. 804 */ 805 @ColorInt 806 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 807 808 /** 809 * Special value of {@link #color} used as a place holder for an invalid color. 810 * @hide 811 */ 812 @ColorInt 813 public static final int COLOR_INVALID = 1; 814 815 /** 816 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 817 * the notification's presence and contents in untrusted situations (namely, on the secure 818 * lockscreen). 819 * 820 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 821 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 822 * shown in all situations, but the contents are only available if the device is unlocked for 823 * the appropriate user. 824 * 825 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 826 * can be read even in an "insecure" context (that is, above a secure lockscreen). 827 * To modify the public version of this notification—for example, to redact some portions—see 828 * {@link Builder#setPublicVersion(Notification)}. 829 * 830 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 831 * and ticker until the user has bypassed the lockscreen. 832 */ 833 public @Visibility int visibility; 834 835 /** @hide */ 836 @IntDef(prefix = { "VISIBILITY_" }, value = { 837 VISIBILITY_PUBLIC, 838 VISIBILITY_PRIVATE, 839 VISIBILITY_SECRET, 840 }) 841 @Retention(RetentionPolicy.SOURCE) 842 public @interface Visibility {} 843 844 /** 845 * Notification visibility: Show this notification in its entirety on all lockscreens. 846 * 847 * {@see #visibility} 848 */ 849 public static final int VISIBILITY_PUBLIC = 1; 850 851 /** 852 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 853 * private information on secure lockscreens. 854 * 855 * {@see #visibility} 856 */ 857 public static final int VISIBILITY_PRIVATE = 0; 858 859 /** 860 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 861 * 862 * {@see #visibility} 863 */ 864 public static final int VISIBILITY_SECRET = -1; 865 866 /** 867 * @hide 868 */ 869 @IntDef(prefix = "VISIBILITY_", value = { 870 VISIBILITY_PUBLIC, 871 VISIBILITY_PRIVATE, 872 VISIBILITY_SECRET, 873 NotificationManager.VISIBILITY_NO_OVERRIDE 874 }) 875 public @interface NotificationVisibilityOverride{}; 876 877 /** 878 * Notification category: incoming call (voice or video) or similar synchronous communication request. 879 */ 880 public static final String CATEGORY_CALL = "call"; 881 882 /** 883 * Notification category: map turn-by-turn navigation. 884 */ 885 public static final String CATEGORY_NAVIGATION = "navigation"; 886 887 /** 888 * Notification category: incoming direct message (SMS, instant message, etc.). 889 */ 890 public static final String CATEGORY_MESSAGE = "msg"; 891 892 /** 893 * Notification category: asynchronous bulk message (email). 894 */ 895 public static final String CATEGORY_EMAIL = "email"; 896 897 /** 898 * Notification category: calendar event. 899 */ 900 public static final String CATEGORY_EVENT = "event"; 901 902 /** 903 * Notification category: promotion or advertisement. 904 */ 905 public static final String CATEGORY_PROMO = "promo"; 906 907 /** 908 * Notification category: alarm or timer. 909 */ 910 public static final String CATEGORY_ALARM = "alarm"; 911 912 /** 913 * Notification category: progress of a long-running background operation. 914 */ 915 public static final String CATEGORY_PROGRESS = "progress"; 916 917 /** 918 * Notification category: social network or sharing update. 919 */ 920 public static final String CATEGORY_SOCIAL = "social"; 921 922 /** 923 * Notification category: error in background operation or authentication status. 924 */ 925 public static final String CATEGORY_ERROR = "err"; 926 927 /** 928 * Notification category: media transport control for playback. 929 */ 930 public static final String CATEGORY_TRANSPORT = "transport"; 931 932 /** 933 * Notification category: system or device status update. Reserved for system use. 934 */ 935 public static final String CATEGORY_SYSTEM = "sys"; 936 937 /** 938 * Notification category: indication of running background service. 939 */ 940 public static final String CATEGORY_SERVICE = "service"; 941 942 /** 943 * Notification category: a specific, timely recommendation for a single thing. 944 * For example, a news app might want to recommend a news story it believes the user will 945 * want to read next. 946 */ 947 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 948 949 /** 950 * Notification category: ongoing information about device or contextual status. 951 */ 952 public static final String CATEGORY_STATUS = "status"; 953 954 /** 955 * Notification category: user-scheduled reminder. 956 */ 957 public static final String CATEGORY_REMINDER = "reminder"; 958 959 /** 960 * Notification category: extreme car emergencies. 961 * @hide 962 */ 963 @SystemApi 964 public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; 965 966 /** 967 * Notification category: car warnings. 968 * @hide 969 */ 970 @SystemApi 971 public static final String CATEGORY_CAR_WARNING = "car_warning"; 972 973 /** 974 * Notification category: general car system information. 975 * @hide 976 */ 977 @SystemApi 978 public static final String CATEGORY_CAR_INFORMATION = "car_information"; 979 980 /** 981 * Notification category: tracking a user's workout. 982 */ 983 public static final String CATEGORY_WORKOUT = "workout"; 984 985 /** 986 * Notification category: temporarily sharing location. 987 */ 988 public static final String CATEGORY_LOCATION_SHARING = "location_sharing"; 989 990 /** 991 * Notification category: running stopwatch. 992 */ 993 public static final String CATEGORY_STOPWATCH = "stopwatch"; 994 995 /** 996 * Notification category: missed call. 997 */ 998 public static final String CATEGORY_MISSED_CALL = "missed_call"; 999 1000 /** 1001 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 1002 * that best describes this Notification. May be used by the system for ranking and filtering. 1003 */ 1004 public String category; 1005 1006 @UnsupportedAppUsage 1007 private String mGroupKey; 1008 1009 /** 1010 * Get the key used to group this notification into a cluster or stack 1011 * with other notifications on devices which support such rendering. 1012 */ getGroup()1013 public String getGroup() { 1014 return mGroupKey; 1015 } 1016 1017 private String mSortKey; 1018 1019 /** 1020 * Get a sort key that orders this notification among other notifications from the 1021 * same package. This can be useful if an external sort was already applied and an app 1022 * would like to preserve this. Notifications will be sorted lexicographically using this 1023 * value, although providing different priorities in addition to providing sort key may 1024 * cause this value to be ignored. 1025 * 1026 * <p>This sort key can also be used to order members of a notification group. See 1027 * {@link Builder#setGroup}. 1028 * 1029 * @see String#compareTo(String) 1030 */ getSortKey()1031 public String getSortKey() { 1032 return mSortKey; 1033 } 1034 1035 /** 1036 * Additional semantic data to be carried around with this Notification. 1037 * <p> 1038 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 1039 * APIs, and are intended to be used by 1040 * {@link android.service.notification.NotificationListenerService} implementations to extract 1041 * detailed information from notification objects. 1042 */ 1043 public Bundle extras = new Bundle(); 1044 1045 /** 1046 * All pending intents in the notification as the system needs to be able to access them but 1047 * touching the extras bundle in the system process is not safe because the bundle may contain 1048 * custom parcelable objects. 1049 * 1050 * @hide 1051 */ 1052 @UnsupportedAppUsage 1053 public ArraySet<PendingIntent> allPendingIntents; 1054 1055 /** 1056 * Token identifying the notification that is applying doze/bgcheck allowlisting to the 1057 * pending intents inside of it, so only those will get the behavior. 1058 * 1059 * @hide 1060 */ 1061 private IBinder mAllowlistToken; 1062 1063 /** 1064 * Must be set by a process to start associating tokens with Notification objects 1065 * coming in to it. This is set by NotificationManagerService. 1066 * 1067 * @hide 1068 */ 1069 static public IBinder processAllowlistToken; 1070 1071 /** 1072 * {@link #extras} key: this is the title of the notification, 1073 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 1074 */ 1075 public static final String EXTRA_TITLE = "android.title"; 1076 1077 /** 1078 * {@link #extras} key: this is the title of the notification when shown in expanded form, 1079 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 1080 */ 1081 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 1082 1083 /** 1084 * {@link #extras} key: this is the main text payload, as supplied to 1085 * {@link Builder#setContentText(CharSequence)}. 1086 */ 1087 public static final String EXTRA_TEXT = "android.text"; 1088 1089 /** 1090 * {@link #extras} key: this is a third line of text, as supplied to 1091 * {@link Builder#setSubText(CharSequence)}. 1092 */ 1093 public static final String EXTRA_SUB_TEXT = "android.subText"; 1094 1095 /** 1096 * {@link #extras} key: this is the remote input history, as supplied to 1097 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1098 * 1099 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 1100 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 1101 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 1102 * notifications once the other party has responded). 1103 * 1104 * The extra with this key is of type CharSequence[] and contains the most recent entry at 1105 * the 0 index, the second most recent at the 1 index, etc. 1106 * 1107 * @see Builder#setRemoteInputHistory(CharSequence[]) 1108 */ 1109 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 1110 1111 1112 /** 1113 * {@link #extras} key: this is a remote input history which can include media messages 1114 * in addition to text, as supplied to 1115 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or 1116 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1117 * 1118 * SystemUI can populate this through 1119 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs 1120 * that have been sent through a {@link RemoteInput} of this Notification. These items can 1121 * represent either media content (specified by a URI and a MIME type) or a text message 1122 * (described by a CharSequence). 1123 * 1124 * To maintain compatibility, this can also be set by apps with 1125 * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a 1126 * {@link RemoteInputHistoryItem} for each of the provided text-only messages. 1127 * 1128 * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most 1129 * recent entry at the 0 index, the second most recent at the 1 index, etc. 1130 * 1131 * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) 1132 * @hide 1133 */ 1134 public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; 1135 1136 /** 1137 * {@link #extras} key: boolean as supplied to 1138 * {@link Builder#setShowRemoteInputSpinner(boolean)}. 1139 * 1140 * If set to true, then the view displaying the remote input history from 1141 * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner. 1142 * 1143 * @see Builder#setShowRemoteInputSpinner(boolean) 1144 * @hide 1145 */ 1146 public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner"; 1147 1148 /** 1149 * {@link #extras} key: boolean as supplied to 1150 * {@link Builder#setHideSmartReplies(boolean)}. 1151 * 1152 * If set to true, then any smart reply buttons will be hidden. 1153 * 1154 * @see Builder#setHideSmartReplies(boolean) 1155 * @hide 1156 */ 1157 public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies"; 1158 1159 /** 1160 * {@link #extras} key: this is a small piece of additional text as supplied to 1161 * {@link Builder#setContentInfo(CharSequence)}. 1162 */ 1163 public static final String EXTRA_INFO_TEXT = "android.infoText"; 1164 1165 /** 1166 * {@link #extras} key: this is a line of summary information intended to be shown 1167 * alongside expanded notifications, as supplied to (e.g.) 1168 * {@link BigTextStyle#setSummaryText(CharSequence)}. 1169 */ 1170 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 1171 1172 /** 1173 * {@link #extras} key: this is the longer text shown in the big form of a 1174 * {@link BigTextStyle} notification, as supplied to 1175 * {@link BigTextStyle#bigText(CharSequence)}. 1176 */ 1177 public static final String EXTRA_BIG_TEXT = "android.bigText"; 1178 1179 /** 1180 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 1181 * supplied to {@link Builder#setSmallIcon(int)}. 1182 * 1183 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources. 1184 */ 1185 @Deprecated 1186 public static final String EXTRA_SMALL_ICON = "android.icon"; 1187 1188 /** 1189 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 1190 * notification payload, as 1191 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 1192 * 1193 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources. 1194 */ 1195 @Deprecated 1196 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 1197 1198 /** 1199 * {@link #extras} key: this is a bitmap to be used instead of the one from 1200 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 1201 * shown in its expanded form, as supplied to 1202 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 1203 */ 1204 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 1205 1206 /** 1207 * {@link #extras} key: this is the progress value supplied to 1208 * {@link Builder#setProgress(int, int, boolean)}. 1209 */ 1210 public static final String EXTRA_PROGRESS = "android.progress"; 1211 1212 /** 1213 * {@link #extras} key: this is the maximum value supplied to 1214 * {@link Builder#setProgress(int, int, boolean)}. 1215 */ 1216 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 1217 1218 /** 1219 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 1220 * {@link Builder#setProgress(int, int, boolean)}. 1221 */ 1222 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 1223 1224 /** 1225 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 1226 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 1227 * {@link Builder#setUsesChronometer(boolean)}. 1228 */ 1229 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 1230 1231 /** 1232 * {@link #extras} key: whether the chronometer set on the notification should count down 1233 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 1234 * This extra is a boolean. The default is false. 1235 */ 1236 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 1237 1238 /** 1239 * {@link #extras} key: whether {@link #when} should be shown, 1240 * as supplied to {@link Builder#setShowWhen(boolean)}. 1241 */ 1242 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 1243 1244 /** 1245 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 1246 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 1247 */ 1248 public static final String EXTRA_PICTURE = "android.picture"; 1249 1250 /** 1251 * {@link #extras} key: this is an {@link Icon} of an image to be 1252 * shown in {@link BigPictureStyle} expanded notifications, supplied to 1253 * {@link BigPictureStyle#bigPicture(Icon)}. 1254 */ 1255 public static final String EXTRA_PICTURE_ICON = "android.pictureIcon"; 1256 1257 /** 1258 * {@link #extras} key: this is a content description of the big picture supplied from 1259 * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to 1260 * {@link BigPictureStyle#setContentDescription(CharSequence)}. 1261 */ 1262 public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = 1263 "android.pictureContentDescription"; 1264 1265 /** 1266 * {@link #extras} key: this is a boolean to indicate that the 1267 * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state 1268 * of a {@link BigPictureStyle} notification. This will replace a 1269 * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided. 1270 */ 1271 public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = 1272 "android.showBigPictureWhenCollapsed"; 1273 1274 /** 1275 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 1276 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 1277 */ 1278 public static final String EXTRA_TEXT_LINES = "android.textLines"; 1279 1280 /** 1281 * {@link #extras} key: A string representing the name of the specific 1282 * {@link android.app.Notification.Style} used to create this notification. 1283 */ 1284 public static final String EXTRA_TEMPLATE = "android.template"; 1285 1286 /** 1287 * {@link #extras} key: A String array containing the people that this notification relates to, 1288 * each of which was supplied to {@link Builder#addPerson(String)}. 1289 * 1290 * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} 1291 */ 1292 public static final String EXTRA_PEOPLE = "android.people"; 1293 1294 /** 1295 * {@link #extras} key: An arrayList of {@link Person} objects containing the people that 1296 * this notification relates to. 1297 */ 1298 public static final String EXTRA_PEOPLE_LIST = "android.people.list"; 1299 1300 /** 1301 * Allow certain system-generated notifications to appear before the device is provisioned. 1302 * Only available to notifications coming from the android package. 1303 * @hide 1304 */ 1305 @SystemApi 1306 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP) 1307 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 1308 1309 /** 1310 * {@link #extras} key: 1311 * flat {@link String} representation of a {@link android.content.ContentUris content URI} 1312 * pointing to an image that can be displayed in the background when the notification is 1313 * selected. Used on television platforms. The URI must point to an image stream suitable for 1314 * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1315 * BitmapFactory.decodeStream}; all other content types will be ignored. 1316 */ 1317 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 1318 1319 /** 1320 * {@link #extras} key: A 1321 * {@link android.media.session.MediaSession.Token} associated with a 1322 * {@link android.app.Notification.MediaStyle} notification. 1323 */ 1324 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 1325 1326 /** 1327 * {@link #extras} key: the indices of actions to be shown in the compact view, 1328 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 1329 */ 1330 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 1331 1332 /** 1333 * {@link #extras} key: the username to be displayed for all messages sent by the user including 1334 * direct replies 1335 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1336 * {@link CharSequence} 1337 * 1338 * @deprecated use {@link #EXTRA_MESSAGING_PERSON} 1339 */ 1340 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 1341 1342 /** 1343 * {@link #extras} key: the person to be displayed for all messages sent by the user including 1344 * direct replies 1345 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1346 * {@link Person} 1347 */ 1348 public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; 1349 1350 /** 1351 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 1352 * represented by a {@link android.app.Notification.MessagingStyle} 1353 */ 1354 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 1355 1356 /** @hide */ 1357 public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon"; 1358 1359 /** @hide */ 1360 public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT = 1361 "android.conversationUnreadMessageCount"; 1362 1363 /** 1364 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 1365 * bundles provided by a 1366 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1367 * array of bundles. 1368 */ 1369 public static final String EXTRA_MESSAGES = "android.messages"; 1370 1371 /** 1372 * {@link #extras} key: an array of 1373 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic} 1374 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a 1375 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1376 * array of bundles. 1377 */ 1378 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; 1379 1380 /** 1381 * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification 1382 * represents a group conversation. 1383 */ 1384 public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; 1385 1386 /** 1387 * {@link #extras} key: the type of call represented by the 1388 * {@link android.app.Notification.CallStyle} notification. This extra is an int. 1389 * @hide 1390 */ 1391 public static final String EXTRA_CALL_TYPE = "android.callType"; 1392 1393 /** 1394 * {@link #extras} key: whether the {@link android.app.Notification.CallStyle} notification 1395 * is for a call that will activate video when answered. This extra is a boolean. 1396 */ 1397 public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo"; 1398 1399 /** 1400 * {@link #extras} key: the person to be displayed as calling for the 1401 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}. 1402 */ 1403 public static final String EXTRA_CALL_PERSON = "android.callPerson"; 1404 1405 /** 1406 * {@link #extras} key: the icon to be displayed as a verification status of the caller on a 1407 * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}. 1408 */ 1409 public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon"; 1410 1411 /** 1412 * {@link #extras} key: the text to be displayed as a verification status of the caller on a 1413 * {@link android.app.Notification.CallStyle} notification. This extra is a 1414 * {@link CharSequence}. 1415 */ 1416 public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText"; 1417 1418 /** 1419 * {@link #extras} key: the intent to be sent when the users answers a 1420 * {@link android.app.Notification.CallStyle} notification. This extra is a 1421 * {@link PendingIntent}. 1422 */ 1423 public static final String EXTRA_ANSWER_INTENT = "android.answerIntent"; 1424 1425 /** 1426 * {@link #extras} key: the intent to be sent when the users declines a 1427 * {@link android.app.Notification.CallStyle} notification. This extra is a 1428 * {@link PendingIntent}. 1429 */ 1430 public static final String EXTRA_DECLINE_INTENT = "android.declineIntent"; 1431 1432 /** 1433 * {@link #extras} key: the intent to be sent when the users hangs up a 1434 * {@link android.app.Notification.CallStyle} notification. This extra is a 1435 * {@link PendingIntent}. 1436 */ 1437 public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent"; 1438 1439 /** 1440 * {@link #extras} key: the color used as a hint for the Answer action button of a 1441 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1442 */ 1443 public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; 1444 1445 /** 1446 * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a 1447 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1448 */ 1449 public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; 1450 1451 /** 1452 * {@link #extras} key: whether the notification should be colorized as 1453 * supplied to {@link Builder#setColorized(boolean)}. 1454 */ 1455 public static final String EXTRA_COLORIZED = "android.colorized"; 1456 1457 /** 1458 * @hide 1459 */ 1460 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 1461 1462 /** 1463 * @hide 1464 */ 1465 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 1466 1467 /** 1468 * @hide 1469 */ 1470 public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images"; 1471 1472 /** 1473 * {@link #extras} key: the audio contents of this notification. 1474 * 1475 * This is for use when rendering the notification on an audio-focused interface; 1476 * the audio contents are a complete sound sample that contains the contents/body of the 1477 * notification. This may be used in substitute of a Text-to-Speech reading of the 1478 * notification. For example if the notification represents a voice message this should point 1479 * to the audio of that message. 1480 * 1481 * The data stored under this key should be a String representation of a Uri that contains the 1482 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 1483 * 1484 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 1485 * has a field for holding data URI. That field can be used for audio. 1486 * See {@code Message#setData}. 1487 * 1488 * Example usage: 1489 * <pre> 1490 * {@code 1491 * Notification.Builder myBuilder = (build your Notification as normal); 1492 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 1493 * } 1494 * </pre> 1495 */ 1496 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 1497 1498 /** @hide */ 1499 @SystemApi 1500 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME) 1501 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 1502 1503 /** 1504 * This is set on the notifications shown by system_server about apps running foreground 1505 * services. It indicates that the notification should be shown 1506 * only if any of the given apps do not already have a properly tagged 1507 * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. 1508 * This is a string array of all package names of the apps. 1509 * @hide 1510 */ 1511 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; 1512 1513 @UnsupportedAppUsage 1514 private Icon mSmallIcon; 1515 @UnsupportedAppUsage 1516 private Icon mLargeIcon; 1517 1518 @UnsupportedAppUsage 1519 private String mChannelId; 1520 private long mTimeout; 1521 1522 private String mShortcutId; 1523 private LocusId mLocusId; 1524 private CharSequence mSettingsText; 1525 1526 private BubbleMetadata mBubbleMetadata; 1527 1528 /** @hide */ 1529 @IntDef(prefix = { "GROUP_ALERT_" }, value = { 1530 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY 1531 }) 1532 @Retention(RetentionPolicy.SOURCE) 1533 public @interface GroupAlertBehavior {} 1534 1535 /** 1536 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 1537 * group with sound or vibration ought to make sound or vibrate (respectively), so this 1538 * notification will not be muted when it is in a group. 1539 */ 1540 public static final int GROUP_ALERT_ALL = 0; 1541 1542 /** 1543 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 1544 * notification in a group should be silenced (no sound or vibration) even if they are posted 1545 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to 1546 * mute this notification if this notification is a group child. This must be applied to all 1547 * children notifications you want to mute. 1548 * 1549 * <p> For example, you might want to use this constant if you post a number of children 1550 * notifications at once (say, after a periodic sync), and only need to notify the user 1551 * audibly once. 1552 */ 1553 public static final int GROUP_ALERT_SUMMARY = 1; 1554 1555 /** 1556 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 1557 * notification in a group should be silenced (no sound or vibration) even if they are 1558 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant 1559 * to mute this notification if this notification is a group summary. 1560 * 1561 * <p>For example, you might want to use this constant if only the children notifications 1562 * in your group have content and the summary is only used to visually group notifications 1563 * rather than to alert the user that new information is available. 1564 */ 1565 public static final int GROUP_ALERT_CHILDREN = 2; 1566 1567 private int mGroupAlertBehavior = GROUP_ALERT_ALL; 1568 1569 /** 1570 * If this notification is being shown as a badge, always show as a number. 1571 */ 1572 public static final int BADGE_ICON_NONE = 0; 1573 1574 /** 1575 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to 1576 * represent this notification. 1577 */ 1578 public static final int BADGE_ICON_SMALL = 1; 1579 1580 /** 1581 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to 1582 * represent this notification. 1583 */ 1584 public static final int BADGE_ICON_LARGE = 2; 1585 private int mBadgeIcon = BADGE_ICON_NONE; 1586 1587 /** 1588 * Determines whether the platform can generate contextual actions for a notification. 1589 */ 1590 private boolean mAllowSystemGeneratedContextualActions = true; 1591 1592 /** 1593 * Structure to encapsulate a named action that can be shown as part of this notification. 1594 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1595 * selected by the user. 1596 * <p> 1597 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1598 * or {@link Notification.Builder#addAction(Notification.Action)} 1599 * to attach actions. 1600 * <p> 1601 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link 1602 * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while 1603 * processing broadcast receivers or services in response to notification action clicks. To 1604 * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself. 1605 */ 1606 public static class Action implements Parcelable { 1607 /** 1608 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of 1609 * {@link RemoteInput}s. 1610 * 1611 * This is intended for {@link RemoteInput}s that only accept data, meaning 1612 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 1613 * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not 1614 * empty. These {@link RemoteInput}s will be ignored by devices that do not 1615 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 1616 * 1617 * You can test if a RemoteInput matches these constraints using 1618 * {@link RemoteInput#isDataOnly}. 1619 */ 1620 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; 1621 1622 /** 1623 * {@link }: No semantic action defined. 1624 */ 1625 public static final int SEMANTIC_ACTION_NONE = 0; 1626 1627 /** 1628 * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies 1629 * may be appropriate. 1630 */ 1631 public static final int SEMANTIC_ACTION_REPLY = 1; 1632 1633 /** 1634 * {@code SemanticAction}: Mark content as read. 1635 */ 1636 public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; 1637 1638 /** 1639 * {@code SemanticAction}: Mark content as unread. 1640 */ 1641 public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; 1642 1643 /** 1644 * {@code SemanticAction}: Delete the content associated with the notification. This 1645 * could mean deleting an email, message, etc. 1646 */ 1647 public static final int SEMANTIC_ACTION_DELETE = 4; 1648 1649 /** 1650 * {@code SemanticAction}: Archive the content associated with the notification. This 1651 * could mean archiving an email, message, etc. 1652 */ 1653 public static final int SEMANTIC_ACTION_ARCHIVE = 5; 1654 1655 /** 1656 * {@code SemanticAction}: Mute the content associated with the notification. This could 1657 * mean silencing a conversation or currently playing media. 1658 */ 1659 public static final int SEMANTIC_ACTION_MUTE = 6; 1660 1661 /** 1662 * {@code SemanticAction}: Unmute the content associated with the notification. This could 1663 * mean un-silencing a conversation or currently playing media. 1664 */ 1665 public static final int SEMANTIC_ACTION_UNMUTE = 7; 1666 1667 /** 1668 * {@code SemanticAction}: Mark content with a thumbs up. 1669 */ 1670 public static final int SEMANTIC_ACTION_THUMBS_UP = 8; 1671 1672 /** 1673 * {@code SemanticAction}: Mark content with a thumbs down. 1674 */ 1675 public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; 1676 1677 /** 1678 * {@code SemanticAction}: Call a contact, group, etc. 1679 */ 1680 public static final int SEMANTIC_ACTION_CALL = 10; 1681 1682 /** 1683 * {@code SemanticAction}: Mark the conversation associated with the notification as a 1684 * priority. Note that this is only for use by the notification assistant services. The 1685 * type will be ignored for actions an app adds to its own notifications. 1686 * @hide 1687 */ 1688 @SystemApi 1689 public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; 1690 1691 /** 1692 * {@code SemanticAction}: Mark content as a potential phishing attempt. 1693 * Note that this is only for use by the notification assistant services. The type will 1694 * be ignored for actions an app adds to its own notifications. 1695 * @hide 1696 */ 1697 @SystemApi 1698 public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; 1699 1700 private final Bundle mExtras; 1701 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1702 private Icon mIcon; 1703 private final RemoteInput[] mRemoteInputs; 1704 private boolean mAllowGeneratedReplies = true; 1705 private final @SemanticAction int mSemanticAction; 1706 private final boolean mIsContextual; 1707 private boolean mAuthenticationRequired; 1708 1709 /** 1710 * Small icon representing the action. 1711 * 1712 * @deprecated Use {@link Action#getIcon()} instead. 1713 */ 1714 @Deprecated 1715 public int icon; 1716 1717 /** 1718 * Title of the action. 1719 */ 1720 public CharSequence title; 1721 1722 /** 1723 * Intent to send when the user invokes this action. May be null, in which case the action 1724 * may be rendered in a disabled presentation by the system UI. 1725 */ 1726 public PendingIntent actionIntent; 1727 Action(Parcel in)1728 private Action(Parcel in) { 1729 if (in.readInt() != 0) { 1730 mIcon = Icon.CREATOR.createFromParcel(in); 1731 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1732 icon = mIcon.getResId(); 1733 } 1734 } 1735 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1736 if (in.readInt() == 1) { 1737 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1738 } 1739 mExtras = Bundle.setDefusable(in.readBundle(), true); 1740 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1741 mAllowGeneratedReplies = in.readInt() == 1; 1742 mSemanticAction = in.readInt(); 1743 mIsContextual = in.readInt() == 1; 1744 mAuthenticationRequired = in.readInt() == 1; 1745 } 1746 1747 /** 1748 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1749 */ 1750 @Deprecated Action(int icon, CharSequence title, PendingIntent intent)1751 public Action(int icon, CharSequence title, PendingIntent intent) { 1752 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, 1753 SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */); 1754 } 1755 1756 /** 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, boolean requireAuth)1757 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1758 RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1759 @SemanticAction int semanticAction, boolean isContextual, 1760 boolean requireAuth) { 1761 this.mIcon = icon; 1762 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1763 this.icon = icon.getResId(); 1764 } 1765 this.title = title; 1766 this.actionIntent = intent; 1767 this.mExtras = extras != null ? extras : new Bundle(); 1768 this.mRemoteInputs = remoteInputs; 1769 this.mAllowGeneratedReplies = allowGeneratedReplies; 1770 this.mSemanticAction = semanticAction; 1771 this.mIsContextual = isContextual; 1772 this.mAuthenticationRequired = requireAuth; 1773 } 1774 1775 /** 1776 * Return an icon representing the action. 1777 */ getIcon()1778 public Icon getIcon() { 1779 if (mIcon == null && icon != 0) { 1780 // you snuck an icon in here without using the builder; let's try to keep it 1781 mIcon = Icon.createWithResource("", icon); 1782 } 1783 return mIcon; 1784 } 1785 1786 /** 1787 * Get additional metadata carried around with this Action. 1788 */ getExtras()1789 public Bundle getExtras() { 1790 return mExtras; 1791 } 1792 1793 /** 1794 * Return whether the platform should automatically generate possible replies for this 1795 * {@link Action} 1796 */ getAllowGeneratedReplies()1797 public boolean getAllowGeneratedReplies() { 1798 return mAllowGeneratedReplies; 1799 } 1800 1801 /** 1802 * Get the list of inputs to be collected from the user when this action is sent. 1803 * May return null if no remote inputs were added. Only returns inputs which accept 1804 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 1805 */ getRemoteInputs()1806 public RemoteInput[] getRemoteInputs() { 1807 return mRemoteInputs; 1808 } 1809 1810 /** 1811 * Returns the {@code SemanticAction} associated with this {@link Action}. A 1812 * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do 1813 * (eg. reply, mark as read, delete, etc). 1814 */ getSemanticAction()1815 public @SemanticAction int getSemanticAction() { 1816 return mSemanticAction; 1817 } 1818 1819 /** 1820 * Returns whether this is a contextual Action, i.e. whether the action is dependent on the 1821 * notification message body. An example of a contextual action could be an action opening a 1822 * map application with an address shown in the notification. 1823 */ isContextual()1824 public boolean isContextual() { 1825 return mIsContextual; 1826 } 1827 1828 /** 1829 * Get the list of inputs to be collected from the user that ONLY accept data when this 1830 * action is sent. These remote inputs are guaranteed to return true on a call to 1831 * {@link RemoteInput#isDataOnly}. 1832 * 1833 * Returns null if there are no data-only remote inputs. 1834 * 1835 * This method exists so that legacy RemoteInput collectors that pre-date the addition 1836 * of non-textual RemoteInputs do not access these remote inputs. 1837 */ getDataOnlyRemoteInputs()1838 public RemoteInput[] getDataOnlyRemoteInputs() { 1839 return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 1840 } 1841 1842 /** 1843 * Returns whether the OS should only send this action's {@link PendingIntent} on an 1844 * unlocked device. 1845 * 1846 * If the device is locked when the action is invoked, the OS should show the keyguard and 1847 * require successful authentication before invoking the intent. 1848 */ isAuthenticationRequired()1849 public boolean isAuthenticationRequired() { 1850 return mAuthenticationRequired; 1851 } 1852 1853 /** 1854 * Builder class for {@link Action} objects. 1855 */ 1856 public static final class Builder { 1857 @Nullable private final Icon mIcon; 1858 @Nullable private final CharSequence mTitle; 1859 @Nullable private final PendingIntent mIntent; 1860 private boolean mAllowGeneratedReplies = true; 1861 @NonNull private final Bundle mExtras; 1862 @Nullable private ArrayList<RemoteInput> mRemoteInputs; 1863 private @SemanticAction int mSemanticAction; 1864 private boolean mIsContextual; 1865 private boolean mAuthenticationRequired; 1866 1867 /** 1868 * Construct a new builder for {@link Action} object. 1869 * @param icon icon to show for this action 1870 * @param title the title of the action 1871 * @param intent the {@link PendingIntent} to fire when users trigger this action 1872 */ 1873 @Deprecated Builder(int icon, CharSequence title, PendingIntent intent)1874 public Builder(int icon, CharSequence title, PendingIntent intent) { 1875 this(Icon.createWithResource("", icon), title, intent); 1876 } 1877 1878 /** 1879 * Construct a new builder for {@link Action} object. 1880 * 1881 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 1882 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 1883 * while processing broadcast receivers or services in response to notification action 1884 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the 1885 * activity itself. 1886 * 1887 * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or 1888 * both are displayed or required, depends on where and how the action is used, and the 1889 * {@link Style} applied to the Notification. 1890 * 1891 * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a 1892 * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed 1893 * with an altered in luminance to ensure proper contrast within the Notification. 1894 * 1895 * @param icon icon to show for this action 1896 * @param title the title of the action 1897 * @param intent the {@link PendingIntent} to fire when users trigger this action 1898 */ Builder(Icon icon, CharSequence title, PendingIntent intent)1899 public Builder(Icon icon, CharSequence title, PendingIntent intent) { 1900 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false); 1901 } 1902 1903 /** 1904 * Construct a new builder for {@link Action} object using the fields from an 1905 * {@link Action}. 1906 * @param action the action to read fields from. 1907 */ Builder(Action action)1908 public Builder(Action action) { 1909 this(action.getIcon(), action.title, action.actionIntent, 1910 new Bundle(action.mExtras), action.getRemoteInputs(), 1911 action.getAllowGeneratedReplies(), action.getSemanticAction(), 1912 action.isAuthenticationRequired()); 1913 } 1914 Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1915 private Builder(@Nullable Icon icon, @Nullable CharSequence title, 1916 @Nullable PendingIntent intent, @NonNull Bundle extras, 1917 @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1918 @SemanticAction int semanticAction, boolean authRequired) { 1919 mIcon = icon; 1920 mTitle = title; 1921 mIntent = intent; 1922 mExtras = extras; 1923 if (remoteInputs != null) { 1924 mRemoteInputs = new ArrayList<>(remoteInputs.length); 1925 Collections.addAll(mRemoteInputs, remoteInputs); 1926 } 1927 mAllowGeneratedReplies = allowGeneratedReplies; 1928 mSemanticAction = semanticAction; 1929 mAuthenticationRequired = authRequired; 1930 } 1931 1932 /** 1933 * Merge additional metadata into this builder. 1934 * 1935 * <p>Values within the Bundle will replace existing extras values in this Builder. 1936 * 1937 * @see Notification.Action#extras 1938 */ 1939 @NonNull addExtras(Bundle extras)1940 public Builder addExtras(Bundle extras) { 1941 if (extras != null) { 1942 mExtras.putAll(extras); 1943 } 1944 return this; 1945 } 1946 1947 /** 1948 * Get the metadata Bundle used by this Builder. 1949 * 1950 * <p>The returned Bundle is shared with this Builder. 1951 */ 1952 @NonNull getExtras()1953 public Bundle getExtras() { 1954 return mExtras; 1955 } 1956 1957 /** 1958 * Add an input to be collected from the user when this action is sent. 1959 * Response values can be retrieved from the fired intent by using the 1960 * {@link RemoteInput#getResultsFromIntent} function. 1961 * @param remoteInput a {@link RemoteInput} to add to the action 1962 * @return this object for method chaining 1963 */ 1964 @NonNull addRemoteInput(RemoteInput remoteInput)1965 public Builder addRemoteInput(RemoteInput remoteInput) { 1966 if (mRemoteInputs == null) { 1967 mRemoteInputs = new ArrayList<RemoteInput>(); 1968 } 1969 mRemoteInputs.add(remoteInput); 1970 return this; 1971 } 1972 1973 /** 1974 * Set whether the platform should automatically generate possible replies to add to 1975 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 1976 * {@link RemoteInput}, this has no effect. 1977 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 1978 * otherwise 1979 * @return this object for method chaining 1980 * The default value is {@code true} 1981 */ 1982 @NonNull setAllowGeneratedReplies(boolean allowGeneratedReplies)1983 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 1984 mAllowGeneratedReplies = allowGeneratedReplies; 1985 return this; 1986 } 1987 1988 /** 1989 * Sets the {@code SemanticAction} for this {@link Action}. A 1990 * {@code SemanticAction} denotes what an {@link Action}'s 1991 * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). 1992 * @param semanticAction a SemanticAction defined within {@link Action} with 1993 * {@code SEMANTIC_ACTION_} prefixes 1994 * @return this object for method chaining 1995 */ 1996 @NonNull setSemanticAction(@emanticAction int semanticAction)1997 public Builder setSemanticAction(@SemanticAction int semanticAction) { 1998 mSemanticAction = semanticAction; 1999 return this; 2000 } 2001 2002 /** 2003 * Sets whether this {@link Action} is a contextual action, i.e. whether the action is 2004 * dependent on the notification message body. An example of a contextual action could 2005 * be an action opening a map application with an address shown in the notification. 2006 */ 2007 @NonNull setContextual(boolean isContextual)2008 public Builder setContextual(boolean isContextual) { 2009 mIsContextual = isContextual; 2010 return this; 2011 } 2012 2013 /** 2014 * Apply an extender to this action builder. Extenders may be used to add 2015 * metadata or change options on this builder. 2016 */ 2017 @NonNull extend(Extender extender)2018 public Builder extend(Extender extender) { 2019 extender.extend(this); 2020 return this; 2021 } 2022 2023 /** 2024 * Sets whether the OS should only send this action's {@link PendingIntent} on an 2025 * unlocked device. 2026 * 2027 * If this is true and the device is locked when the action is invoked, the OS will 2028 * show the keyguard and require successful authentication before invoking the intent. 2029 * If this is false and the device is locked, the OS will decide whether authentication 2030 * should be required. 2031 */ 2032 @NonNull setAuthenticationRequired(boolean authenticationRequired)2033 public Builder setAuthenticationRequired(boolean authenticationRequired) { 2034 mAuthenticationRequired = authenticationRequired; 2035 return this; 2036 } 2037 2038 /** 2039 * Throws an NPE if we are building a contextual action missing one of the fields 2040 * necessary to display the action. 2041 */ checkContextualActionNullFields()2042 private void checkContextualActionNullFields() { 2043 if (!mIsContextual) return; 2044 2045 if (mIcon == null) { 2046 throw new NullPointerException("Contextual Actions must contain a valid icon"); 2047 } 2048 2049 if (mIntent == null) { 2050 throw new NullPointerException( 2051 "Contextual Actions must contain a valid PendingIntent"); 2052 } 2053 } 2054 2055 /** 2056 * Combine all of the options that have been set and return a new {@link Action} 2057 * object. 2058 * @return the built action 2059 */ 2060 @NonNull build()2061 public Action build() { 2062 checkContextualActionNullFields(); 2063 2064 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>(); 2065 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle( 2066 mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 2067 if (previousDataInputs != null) { 2068 for (RemoteInput input : previousDataInputs) { 2069 dataOnlyInputs.add(input); 2070 } 2071 } 2072 List<RemoteInput> textInputs = new ArrayList<>(); 2073 if (mRemoteInputs != null) { 2074 for (RemoteInput input : mRemoteInputs) { 2075 if (input.isDataOnly()) { 2076 dataOnlyInputs.add(input); 2077 } else { 2078 textInputs.add(input); 2079 } 2080 } 2081 } 2082 if (!dataOnlyInputs.isEmpty()) { 2083 RemoteInput[] dataInputsArr = 2084 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 2085 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); 2086 } 2087 RemoteInput[] textInputsArr = textInputs.isEmpty() 2088 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 2089 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 2090 mAllowGeneratedReplies, mSemanticAction, mIsContextual, 2091 mAuthenticationRequired); 2092 } 2093 } 2094 2095 @Override clone()2096 public Action clone() { 2097 return new Action( 2098 getIcon(), 2099 title, 2100 actionIntent, // safe to alias 2101 mExtras == null ? new Bundle() : new Bundle(mExtras), 2102 getRemoteInputs(), 2103 getAllowGeneratedReplies(), 2104 getSemanticAction(), 2105 isContextual(), 2106 isAuthenticationRequired()); 2107 } 2108 2109 @Override describeContents()2110 public int describeContents() { 2111 return 0; 2112 } 2113 2114 @Override writeToParcel(Parcel out, int flags)2115 public void writeToParcel(Parcel out, int flags) { 2116 final Icon ic = getIcon(); 2117 if (ic != null) { 2118 out.writeInt(1); 2119 ic.writeToParcel(out, 0); 2120 } else { 2121 out.writeInt(0); 2122 } 2123 TextUtils.writeToParcel(title, out, flags); 2124 if (actionIntent != null) { 2125 out.writeInt(1); 2126 actionIntent.writeToParcel(out, flags); 2127 } else { 2128 out.writeInt(0); 2129 } 2130 out.writeBundle(mExtras); 2131 out.writeTypedArray(mRemoteInputs, flags); 2132 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 2133 out.writeInt(mSemanticAction); 2134 out.writeInt(mIsContextual ? 1 : 0); 2135 out.writeInt(mAuthenticationRequired ? 1 : 0); 2136 } 2137 2138 public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR = 2139 new Parcelable.Creator<Action>() { 2140 public Action createFromParcel(Parcel in) { 2141 return new Action(in); 2142 } 2143 public Action[] newArray(int size) { 2144 return new Action[size]; 2145 } 2146 }; 2147 2148 /** 2149 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 2150 * metadata or change options on an action builder. 2151 */ 2152 public interface Extender { 2153 /** 2154 * Apply this extender to a notification action builder. 2155 * @param builder the builder to be modified. 2156 * @return the build object for chaining. 2157 */ extend(Builder builder)2158 public Builder extend(Builder builder); 2159 } 2160 2161 /** 2162 * Wearable extender for notification actions. To add extensions to an action, 2163 * create a new {@link android.app.Notification.Action.WearableExtender} object using 2164 * the {@code WearableExtender()} constructor and apply it to a 2165 * {@link android.app.Notification.Action.Builder} using 2166 * {@link android.app.Notification.Action.Builder#extend}. 2167 * 2168 * <pre class="prettyprint"> 2169 * Notification.Action action = new Notification.Action.Builder( 2170 * R.drawable.archive_all, "Archive all", actionIntent) 2171 * .extend(new Notification.Action.WearableExtender() 2172 * .setAvailableOffline(false)) 2173 * .build();</pre> 2174 */ 2175 public static final class WearableExtender implements Extender { 2176 /** Notification action extra which contains wearable extensions */ 2177 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 2178 2179 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 2180 private static final String KEY_FLAGS = "flags"; 2181 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 2182 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 2183 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 2184 2185 // Flags bitwise-ored to mFlags 2186 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 2187 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 2188 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 2189 2190 // Default value for flags integer 2191 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 2192 2193 private int mFlags = DEFAULT_FLAGS; 2194 2195 private CharSequence mInProgressLabel; 2196 private CharSequence mConfirmLabel; 2197 private CharSequence mCancelLabel; 2198 2199 /** 2200 * Create a {@link android.app.Notification.Action.WearableExtender} with default 2201 * options. 2202 */ WearableExtender()2203 public WearableExtender() { 2204 } 2205 2206 /** 2207 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 2208 * wearable options present in an existing notification action. 2209 * @param action the notification action to inspect. 2210 */ WearableExtender(Action action)2211 public WearableExtender(Action action) { 2212 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 2213 if (wearableBundle != null) { 2214 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 2215 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 2216 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 2217 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 2218 } 2219 } 2220 2221 /** 2222 * Apply wearable extensions to a notification action that is being built. This is 2223 * typically called by the {@link android.app.Notification.Action.Builder#extend} 2224 * method of {@link android.app.Notification.Action.Builder}. 2225 */ 2226 @Override extend(Action.Builder builder)2227 public Action.Builder extend(Action.Builder builder) { 2228 Bundle wearableBundle = new Bundle(); 2229 2230 if (mFlags != DEFAULT_FLAGS) { 2231 wearableBundle.putInt(KEY_FLAGS, mFlags); 2232 } 2233 if (mInProgressLabel != null) { 2234 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 2235 } 2236 if (mConfirmLabel != null) { 2237 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 2238 } 2239 if (mCancelLabel != null) { 2240 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 2241 } 2242 2243 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 2244 return builder; 2245 } 2246 2247 @Override clone()2248 public WearableExtender clone() { 2249 WearableExtender that = new WearableExtender(); 2250 that.mFlags = this.mFlags; 2251 that.mInProgressLabel = this.mInProgressLabel; 2252 that.mConfirmLabel = this.mConfirmLabel; 2253 that.mCancelLabel = this.mCancelLabel; 2254 return that; 2255 } 2256 2257 /** 2258 * Set whether this action is available when the wearable device is not connected to 2259 * a companion device. The user can still trigger this action when the wearable device is 2260 * offline, but a visual hint will indicate that the action may not be available. 2261 * Defaults to true. 2262 */ setAvailableOffline(boolean availableOffline)2263 public WearableExtender setAvailableOffline(boolean availableOffline) { 2264 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 2265 return this; 2266 } 2267 2268 /** 2269 * Get whether this action is available when the wearable device is not connected to 2270 * a companion device. The user can still trigger this action when the wearable device is 2271 * offline, but a visual hint will indicate that the action may not be available. 2272 * Defaults to true. 2273 */ isAvailableOffline()2274 public boolean isAvailableOffline() { 2275 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 2276 } 2277 setFlag(int mask, boolean value)2278 private void setFlag(int mask, boolean value) { 2279 if (value) { 2280 mFlags |= mask; 2281 } else { 2282 mFlags &= ~mask; 2283 } 2284 } 2285 2286 /** 2287 * Set a label to display while the wearable is preparing to automatically execute the 2288 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2289 * 2290 * @param label the label to display while the action is being prepared to execute 2291 * @return this object for method chaining 2292 */ 2293 @Deprecated setInProgressLabel(CharSequence label)2294 public WearableExtender setInProgressLabel(CharSequence label) { 2295 mInProgressLabel = label; 2296 return this; 2297 } 2298 2299 /** 2300 * Get the label to display while the wearable is preparing to automatically execute 2301 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2302 * 2303 * @return the label to display while the action is being prepared to execute 2304 */ 2305 @Deprecated getInProgressLabel()2306 public CharSequence getInProgressLabel() { 2307 return mInProgressLabel; 2308 } 2309 2310 /** 2311 * Set a label to display to confirm that the action should be executed. 2312 * This is usually an imperative verb like "Send". 2313 * 2314 * @param label the label to confirm the action should be executed 2315 * @return this object for method chaining 2316 */ 2317 @Deprecated setConfirmLabel(CharSequence label)2318 public WearableExtender setConfirmLabel(CharSequence label) { 2319 mConfirmLabel = label; 2320 return this; 2321 } 2322 2323 /** 2324 * Get the label to display to confirm that the action should be executed. 2325 * This is usually an imperative verb like "Send". 2326 * 2327 * @return the label to confirm the action should be executed 2328 */ 2329 @Deprecated getConfirmLabel()2330 public CharSequence getConfirmLabel() { 2331 return mConfirmLabel; 2332 } 2333 2334 /** 2335 * Set a label to display to cancel the action. 2336 * This is usually an imperative verb, like "Cancel". 2337 * 2338 * @param label the label to display to cancel the action 2339 * @return this object for method chaining 2340 */ 2341 @Deprecated setCancelLabel(CharSequence label)2342 public WearableExtender setCancelLabel(CharSequence label) { 2343 mCancelLabel = label; 2344 return this; 2345 } 2346 2347 /** 2348 * Get the label to display to cancel the action. 2349 * This is usually an imperative verb like "Cancel". 2350 * 2351 * @return the label to display to cancel the action 2352 */ 2353 @Deprecated getCancelLabel()2354 public CharSequence getCancelLabel() { 2355 return mCancelLabel; 2356 } 2357 2358 /** 2359 * Set a hint that this Action will launch an {@link Activity} directly, telling the 2360 * platform that it can generate the appropriate transitions. 2361 * @param hintLaunchesActivity {@code true} if the content intent will launch 2362 * an activity and transitions should be generated, false otherwise. 2363 * @return this object for method chaining 2364 */ setHintLaunchesActivity( boolean hintLaunchesActivity)2365 public WearableExtender setHintLaunchesActivity( 2366 boolean hintLaunchesActivity) { 2367 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 2368 return this; 2369 } 2370 2371 /** 2372 * Get a hint that this Action will launch an {@link Activity} directly, telling the 2373 * platform that it can generate the appropriate transitions 2374 * @return {@code true} if the content intent will launch an activity and transitions 2375 * should be generated, false otherwise. The default value is {@code false} if this was 2376 * never set. 2377 */ getHintLaunchesActivity()2378 public boolean getHintLaunchesActivity() { 2379 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 2380 } 2381 2382 /** 2383 * Set a hint that this Action should be displayed inline. 2384 * 2385 * @param hintDisplayInline {@code true} if action should be displayed inline, false 2386 * otherwise 2387 * @return this object for method chaining 2388 */ setHintDisplayActionInline( boolean hintDisplayInline)2389 public WearableExtender setHintDisplayActionInline( 2390 boolean hintDisplayInline) { 2391 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 2392 return this; 2393 } 2394 2395 /** 2396 * Get a hint that this Action should be displayed inline. 2397 * 2398 * @return {@code true} if the Action should be displayed inline, {@code false} 2399 * otherwise. The default value is {@code false} if this was never set. 2400 */ getHintDisplayActionInline()2401 public boolean getHintDisplayActionInline() { 2402 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 2403 } 2404 } 2405 2406 /** 2407 * Provides meaning to an {@link Action} that hints at what the associated 2408 * {@link PendingIntent} will do. For example, an {@link Action} with a 2409 * {@link PendingIntent} that replies to a text message notification may have the 2410 * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. 2411 * 2412 * @hide 2413 */ 2414 @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { 2415 SEMANTIC_ACTION_NONE, 2416 SEMANTIC_ACTION_REPLY, 2417 SEMANTIC_ACTION_MARK_AS_READ, 2418 SEMANTIC_ACTION_MARK_AS_UNREAD, 2419 SEMANTIC_ACTION_DELETE, 2420 SEMANTIC_ACTION_ARCHIVE, 2421 SEMANTIC_ACTION_MUTE, 2422 SEMANTIC_ACTION_UNMUTE, 2423 SEMANTIC_ACTION_THUMBS_UP, 2424 SEMANTIC_ACTION_THUMBS_DOWN, 2425 SEMANTIC_ACTION_CALL, 2426 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY, 2427 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING 2428 }) 2429 @Retention(RetentionPolicy.SOURCE) 2430 public @interface SemanticAction {} 2431 } 2432 2433 /** 2434 * Array of all {@link Action} structures attached to this notification by 2435 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 2436 * {@link android.service.notification.NotificationListenerService} that provide an alternative 2437 * interface for invoking actions. 2438 */ 2439 public Action[] actions; 2440 2441 /** 2442 * Replacement version of this notification whose content will be shown 2443 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 2444 * and {@link #VISIBILITY_PUBLIC}. 2445 */ 2446 public Notification publicVersion; 2447 2448 /** 2449 * Constructs a Notification object with default values. 2450 * You might want to consider using {@link Builder} instead. 2451 */ Notification()2452 public Notification() 2453 { 2454 this.when = System.currentTimeMillis(); 2455 this.creationTime = System.currentTimeMillis(); 2456 this.priority = PRIORITY_DEFAULT; 2457 } 2458 2459 /** 2460 * @hide 2461 */ 2462 @UnsupportedAppUsage Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2463 public Notification(Context context, int icon, CharSequence tickerText, long when, 2464 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 2465 { 2466 new Builder(context) 2467 .setWhen(when) 2468 .setSmallIcon(icon) 2469 .setTicker(tickerText) 2470 .setContentTitle(contentTitle) 2471 .setContentText(contentText) 2472 .setContentIntent(PendingIntent.getActivity( 2473 context, 0, contentIntent, PendingIntent.FLAG_MUTABLE)) 2474 .buildInto(this); 2475 } 2476 2477 /** 2478 * Constructs a Notification object with the information needed to 2479 * have a status bar icon without the standard expanded view. 2480 * 2481 * @param icon The resource id of the icon to put in the status bar. 2482 * @param tickerText The text that flows by in the status bar when the notification first 2483 * activates. 2484 * @param when The time to show in the time field. In the System.currentTimeMillis 2485 * timebase. 2486 * 2487 * @deprecated Use {@link Builder} instead. 2488 */ 2489 @Deprecated Notification(int icon, CharSequence tickerText, long when)2490 public Notification(int icon, CharSequence tickerText, long when) 2491 { 2492 this.icon = icon; 2493 this.tickerText = tickerText; 2494 this.when = when; 2495 this.creationTime = System.currentTimeMillis(); 2496 } 2497 2498 /** 2499 * Unflatten the notification from a parcel. 2500 */ 2501 @SuppressWarnings("unchecked") Notification(Parcel parcel)2502 public Notification(Parcel parcel) { 2503 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 2504 // intents in extras are always written as the last entry. 2505 readFromParcelImpl(parcel); 2506 // Must be read last! 2507 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 2508 } 2509 readFromParcelImpl(Parcel parcel)2510 private void readFromParcelImpl(Parcel parcel) 2511 { 2512 int version = parcel.readInt(); 2513 2514 mAllowlistToken = parcel.readStrongBinder(); 2515 if (mAllowlistToken == null) { 2516 mAllowlistToken = processAllowlistToken; 2517 } 2518 // Propagate this token to all pending intents that are unmarshalled from the parcel. 2519 parcel.setClassCookie(PendingIntent.class, mAllowlistToken); 2520 2521 when = parcel.readLong(); 2522 creationTime = parcel.readLong(); 2523 if (parcel.readInt() != 0) { 2524 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 2525 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 2526 icon = mSmallIcon.getResId(); 2527 } 2528 } 2529 number = parcel.readInt(); 2530 if (parcel.readInt() != 0) { 2531 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2532 } 2533 if (parcel.readInt() != 0) { 2534 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2535 } 2536 if (parcel.readInt() != 0) { 2537 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2538 } 2539 if (parcel.readInt() != 0) { 2540 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 2541 } 2542 if (parcel.readInt() != 0) { 2543 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 2544 } 2545 if (parcel.readInt() != 0) { 2546 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 2547 } 2548 defaults = parcel.readInt(); 2549 flags = parcel.readInt(); 2550 if (parcel.readInt() != 0) { 2551 sound = Uri.CREATOR.createFromParcel(parcel); 2552 } 2553 2554 audioStreamType = parcel.readInt(); 2555 if (parcel.readInt() != 0) { 2556 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 2557 } 2558 vibrate = parcel.createLongArray(); 2559 ledARGB = parcel.readInt(); 2560 ledOnMS = parcel.readInt(); 2561 ledOffMS = parcel.readInt(); 2562 iconLevel = parcel.readInt(); 2563 2564 if (parcel.readInt() != 0) { 2565 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2566 } 2567 2568 priority = parcel.readInt(); 2569 2570 category = parcel.readString8(); 2571 2572 mGroupKey = parcel.readString8(); 2573 2574 mSortKey = parcel.readString8(); 2575 2576 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 2577 fixDuplicateExtras(); 2578 2579 actions = parcel.createTypedArray(Action.CREATOR); // may be null 2580 2581 if (parcel.readInt() != 0) { 2582 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2583 } 2584 2585 if (parcel.readInt() != 0) { 2586 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2587 } 2588 2589 visibility = parcel.readInt(); 2590 2591 if (parcel.readInt() != 0) { 2592 publicVersion = Notification.CREATOR.createFromParcel(parcel); 2593 } 2594 2595 color = parcel.readInt(); 2596 2597 if (parcel.readInt() != 0) { 2598 mChannelId = parcel.readString8(); 2599 } 2600 mTimeout = parcel.readLong(); 2601 2602 if (parcel.readInt() != 0) { 2603 mShortcutId = parcel.readString8(); 2604 } 2605 2606 if (parcel.readInt() != 0) { 2607 mLocusId = LocusId.CREATOR.createFromParcel(parcel); 2608 } 2609 2610 mBadgeIcon = parcel.readInt(); 2611 2612 if (parcel.readInt() != 0) { 2613 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2614 } 2615 2616 mGroupAlertBehavior = parcel.readInt(); 2617 if (parcel.readInt() != 0) { 2618 mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel); 2619 } 2620 2621 mAllowSystemGeneratedContextualActions = parcel.readBoolean(); 2622 2623 mFgsDeferBehavior = parcel.readInt(); 2624 } 2625 2626 @Override clone()2627 public Notification clone() { 2628 Notification that = new Notification(); 2629 cloneInto(that, true); 2630 return that; 2631 } 2632 2633 /** 2634 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 2635 * of this into that. 2636 * @hide 2637 */ cloneInto(Notification that, boolean heavy)2638 public void cloneInto(Notification that, boolean heavy) { 2639 that.mAllowlistToken = this.mAllowlistToken; 2640 that.when = this.when; 2641 that.creationTime = this.creationTime; 2642 that.mSmallIcon = this.mSmallIcon; 2643 that.number = this.number; 2644 2645 // PendingIntents are global, so there's no reason (or way) to clone them. 2646 that.contentIntent = this.contentIntent; 2647 that.deleteIntent = this.deleteIntent; 2648 that.fullScreenIntent = this.fullScreenIntent; 2649 2650 if (this.tickerText != null) { 2651 that.tickerText = this.tickerText.toString(); 2652 } 2653 if (heavy && this.tickerView != null) { 2654 that.tickerView = this.tickerView.clone(); 2655 } 2656 if (heavy && this.contentView != null) { 2657 that.contentView = this.contentView.clone(); 2658 } 2659 if (heavy && this.mLargeIcon != null) { 2660 that.mLargeIcon = this.mLargeIcon; 2661 } 2662 that.iconLevel = this.iconLevel; 2663 that.sound = this.sound; // android.net.Uri is immutable 2664 that.audioStreamType = this.audioStreamType; 2665 if (this.audioAttributes != null) { 2666 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 2667 } 2668 2669 final long[] vibrate = this.vibrate; 2670 if (vibrate != null) { 2671 final int N = vibrate.length; 2672 final long[] vib = that.vibrate = new long[N]; 2673 System.arraycopy(vibrate, 0, vib, 0, N); 2674 } 2675 2676 that.ledARGB = this.ledARGB; 2677 that.ledOnMS = this.ledOnMS; 2678 that.ledOffMS = this.ledOffMS; 2679 that.defaults = this.defaults; 2680 2681 that.flags = this.flags; 2682 2683 that.priority = this.priority; 2684 2685 that.category = this.category; 2686 2687 that.mGroupKey = this.mGroupKey; 2688 2689 that.mSortKey = this.mSortKey; 2690 2691 if (this.extras != null) { 2692 try { 2693 that.extras = new Bundle(this.extras); 2694 // will unparcel 2695 that.extras.size(); 2696 } catch (BadParcelableException e) { 2697 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 2698 that.extras = null; 2699 } 2700 } 2701 2702 if (!ArrayUtils.isEmpty(allPendingIntents)) { 2703 that.allPendingIntents = new ArraySet<>(allPendingIntents); 2704 } 2705 2706 if (this.actions != null) { 2707 that.actions = new Action[this.actions.length]; 2708 for(int i=0; i<this.actions.length; i++) { 2709 if ( this.actions[i] != null) { 2710 that.actions[i] = this.actions[i].clone(); 2711 } 2712 } 2713 } 2714 2715 if (heavy && this.bigContentView != null) { 2716 that.bigContentView = this.bigContentView.clone(); 2717 } 2718 2719 if (heavy && this.headsUpContentView != null) { 2720 that.headsUpContentView = this.headsUpContentView.clone(); 2721 } 2722 2723 that.visibility = this.visibility; 2724 2725 if (this.publicVersion != null) { 2726 that.publicVersion = new Notification(); 2727 this.publicVersion.cloneInto(that.publicVersion, heavy); 2728 } 2729 2730 that.color = this.color; 2731 2732 that.mChannelId = this.mChannelId; 2733 that.mTimeout = this.mTimeout; 2734 that.mShortcutId = this.mShortcutId; 2735 that.mLocusId = this.mLocusId; 2736 that.mBadgeIcon = this.mBadgeIcon; 2737 that.mSettingsText = this.mSettingsText; 2738 that.mGroupAlertBehavior = this.mGroupAlertBehavior; 2739 that.mFgsDeferBehavior = this.mFgsDeferBehavior; 2740 that.mBubbleMetadata = this.mBubbleMetadata; 2741 that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; 2742 2743 if (!heavy) { 2744 that.lightenPayload(); // will clean out extras 2745 } 2746 } 2747 visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2748 private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) { 2749 if (icon == null) return; 2750 final int iconType = icon.getType(); 2751 if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) { 2752 visitor.accept(icon.getUri()); 2753 } 2754 } 2755 2756 /** 2757 * Note all {@link Uri} that are referenced internally, with the expectation 2758 * that Uri permission grants will need to be issued to ensure the recipient 2759 * of this object is able to render its contents. 2760 * 2761 * @hide 2762 */ visitUris(@onNull Consumer<Uri> visitor)2763 public void visitUris(@NonNull Consumer<Uri> visitor) { 2764 visitor.accept(sound); 2765 2766 if (tickerView != null) tickerView.visitUris(visitor); 2767 if (contentView != null) contentView.visitUris(visitor); 2768 if (bigContentView != null) bigContentView.visitUris(visitor); 2769 if (headsUpContentView != null) headsUpContentView.visitUris(visitor); 2770 2771 visitIconUri(visitor, mSmallIcon); 2772 visitIconUri(visitor, mLargeIcon); 2773 2774 if (actions != null) { 2775 for (Action action : actions) { 2776 visitIconUri(visitor, action.getIcon()); 2777 } 2778 } 2779 2780 if (extras != null) { 2781 visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG)); 2782 visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON)); 2783 2784 // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a 2785 // String representation of a Uri, but the previous implementation (and unit test) of 2786 // this method has always treated it as a Uri object. Given the inconsistency, 2787 // supporting both going forward is the safest choice. 2788 Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI); 2789 if (audioContentsUri instanceof Uri) { 2790 visitor.accept((Uri) audioContentsUri); 2791 } else if (audioContentsUri instanceof String) { 2792 visitor.accept(Uri.parse((String) audioContentsUri)); 2793 } 2794 2795 if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 2796 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); 2797 } 2798 2799 ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 2800 if (people != null && !people.isEmpty()) { 2801 for (Person p : people) { 2802 visitor.accept(p.getIconUri()); 2803 } 2804 } 2805 2806 final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON); 2807 if (person != null) { 2808 visitor.accept(person.getIconUri()); 2809 } 2810 } 2811 2812 if (isStyle(MessagingStyle.class) && extras != null) { 2813 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 2814 if (!ArrayUtils.isEmpty(messages)) { 2815 for (MessagingStyle.Message message : MessagingStyle.Message 2816 .getMessagesFromBundleArray(messages)) { 2817 visitor.accept(message.getDataUri()); 2818 2819 Person senderPerson = message.getSenderPerson(); 2820 if (senderPerson != null) { 2821 visitor.accept(senderPerson.getIconUri()); 2822 } 2823 } 2824 } 2825 2826 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 2827 if (!ArrayUtils.isEmpty(historic)) { 2828 for (MessagingStyle.Message message : MessagingStyle.Message 2829 .getMessagesFromBundleArray(historic)) { 2830 visitor.accept(message.getDataUri()); 2831 2832 Person senderPerson = message.getSenderPerson(); 2833 if (senderPerson != null) { 2834 visitor.accept(senderPerson.getIconUri()); 2835 } 2836 } 2837 } 2838 } 2839 2840 if (mBubbleMetadata != null) { 2841 visitIconUri(visitor, mBubbleMetadata.getIcon()); 2842 } 2843 } 2844 2845 /** 2846 * Removes heavyweight parts of the Notification object for archival or for sending to 2847 * listeners when the full contents are not necessary. 2848 * @hide 2849 */ lightenPayload()2850 public final void lightenPayload() { 2851 tickerView = null; 2852 contentView = null; 2853 bigContentView = null; 2854 headsUpContentView = null; 2855 mLargeIcon = null; 2856 if (extras != null && !extras.isEmpty()) { 2857 final Set<String> keyset = extras.keySet(); 2858 final int N = keyset.size(); 2859 final String[] keys = keyset.toArray(new String[N]); 2860 for (int i=0; i<N; i++) { 2861 final String key = keys[i]; 2862 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) { 2863 continue; 2864 } 2865 final Object obj = extras.get(key); 2866 if (obj != null && 2867 ( obj instanceof Parcelable 2868 || obj instanceof Parcelable[] 2869 || obj instanceof SparseArray 2870 || obj instanceof ArrayList)) { 2871 extras.remove(key); 2872 } 2873 } 2874 } 2875 } 2876 2877 /** 2878 * Make sure this CharSequence is safe to put into a bundle, which basically 2879 * means it had better not be some custom Parcelable implementation. 2880 * @hide 2881 */ safeCharSequence(CharSequence cs)2882 public static CharSequence safeCharSequence(CharSequence cs) { 2883 if (cs == null) return cs; 2884 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 2885 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 2886 } 2887 if (cs instanceof Parcelable) { 2888 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 2889 + " instance is a custom Parcelable and not allowed in Notification"); 2890 return cs.toString(); 2891 } 2892 return removeTextSizeSpans(cs); 2893 } 2894 removeTextSizeSpans(CharSequence charSequence)2895 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 2896 if (charSequence instanceof Spanned) { 2897 Spanned ss = (Spanned) charSequence; 2898 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 2899 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 2900 for (Object span : spans) { 2901 Object resultSpan = span; 2902 if (resultSpan instanceof CharacterStyle) { 2903 resultSpan = ((CharacterStyle) span).getUnderlying(); 2904 } 2905 if (resultSpan instanceof TextAppearanceSpan) { 2906 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 2907 resultSpan = new TextAppearanceSpan( 2908 originalSpan.getFamily(), 2909 originalSpan.getTextStyle(), 2910 -1, 2911 originalSpan.getTextColor(), 2912 originalSpan.getLinkTextColor()); 2913 } else if (resultSpan instanceof RelativeSizeSpan 2914 || resultSpan instanceof AbsoluteSizeSpan) { 2915 continue; 2916 } else { 2917 resultSpan = span; 2918 } 2919 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 2920 ss.getSpanFlags(span)); 2921 } 2922 return builder; 2923 } 2924 return charSequence; 2925 } 2926 describeContents()2927 public int describeContents() { 2928 return 0; 2929 } 2930 2931 /** 2932 * Flatten this notification into a parcel. 2933 */ writeToParcel(Parcel parcel, int flags)2934 public void writeToParcel(Parcel parcel, int flags) { 2935 // We need to mark all pending intents getting into the notification 2936 // system as being put there to later allow the notification ranker 2937 // to launch them and by doing so add the app to the battery saver white 2938 // list for a short period of time. The problem is that the system 2939 // cannot look into the extras as there may be parcelables there that 2940 // the platform does not know how to handle. To go around that we have 2941 // an explicit list of the pending intents in the extras bundle. 2942 final boolean collectPendingIntents = (allPendingIntents == null); 2943 if (collectPendingIntents) { 2944 PendingIntent.setOnMarshaledListener( 2945 (PendingIntent intent, Parcel out, int outFlags) -> { 2946 if (parcel == out) { 2947 synchronized (this) { 2948 if (allPendingIntents == null) { 2949 allPendingIntents = new ArraySet<>(); 2950 } 2951 allPendingIntents.add(intent); 2952 } 2953 } 2954 }); 2955 } 2956 try { 2957 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 2958 // want to intercept all pending events written to the parcel. 2959 writeToParcelImpl(parcel, flags); 2960 synchronized (this) { 2961 // Must be written last! 2962 parcel.writeArraySet(allPendingIntents); 2963 } 2964 } finally { 2965 if (collectPendingIntents) { 2966 PendingIntent.setOnMarshaledListener(null); 2967 } 2968 } 2969 } 2970 writeToParcelImpl(Parcel parcel, int flags)2971 private void writeToParcelImpl(Parcel parcel, int flags) { 2972 parcel.writeInt(1); 2973 2974 parcel.writeStrongBinder(mAllowlistToken); 2975 parcel.writeLong(when); 2976 parcel.writeLong(creationTime); 2977 if (mSmallIcon == null && icon != 0) { 2978 // you snuck an icon in here without using the builder; let's try to keep it 2979 mSmallIcon = Icon.createWithResource("", icon); 2980 } 2981 if (mSmallIcon != null) { 2982 parcel.writeInt(1); 2983 mSmallIcon.writeToParcel(parcel, 0); 2984 } else { 2985 parcel.writeInt(0); 2986 } 2987 parcel.writeInt(number); 2988 if (contentIntent != null) { 2989 parcel.writeInt(1); 2990 contentIntent.writeToParcel(parcel, 0); 2991 } else { 2992 parcel.writeInt(0); 2993 } 2994 if (deleteIntent != null) { 2995 parcel.writeInt(1); 2996 deleteIntent.writeToParcel(parcel, 0); 2997 } else { 2998 parcel.writeInt(0); 2999 } 3000 if (tickerText != null) { 3001 parcel.writeInt(1); 3002 TextUtils.writeToParcel(tickerText, parcel, flags); 3003 } else { 3004 parcel.writeInt(0); 3005 } 3006 if (tickerView != null) { 3007 parcel.writeInt(1); 3008 tickerView.writeToParcel(parcel, 0); 3009 } else { 3010 parcel.writeInt(0); 3011 } 3012 if (contentView != null) { 3013 parcel.writeInt(1); 3014 contentView.writeToParcel(parcel, 0); 3015 } else { 3016 parcel.writeInt(0); 3017 } 3018 if (mLargeIcon == null && largeIcon != null) { 3019 // you snuck an icon in here without using the builder; let's try to keep it 3020 mLargeIcon = Icon.createWithBitmap(largeIcon); 3021 } 3022 if (mLargeIcon != null) { 3023 parcel.writeInt(1); 3024 mLargeIcon.writeToParcel(parcel, 0); 3025 } else { 3026 parcel.writeInt(0); 3027 } 3028 3029 parcel.writeInt(defaults); 3030 parcel.writeInt(this.flags); 3031 3032 if (sound != null) { 3033 parcel.writeInt(1); 3034 sound.writeToParcel(parcel, 0); 3035 } else { 3036 parcel.writeInt(0); 3037 } 3038 parcel.writeInt(audioStreamType); 3039 3040 if (audioAttributes != null) { 3041 parcel.writeInt(1); 3042 audioAttributes.writeToParcel(parcel, 0); 3043 } else { 3044 parcel.writeInt(0); 3045 } 3046 3047 parcel.writeLongArray(vibrate); 3048 parcel.writeInt(ledARGB); 3049 parcel.writeInt(ledOnMS); 3050 parcel.writeInt(ledOffMS); 3051 parcel.writeInt(iconLevel); 3052 3053 if (fullScreenIntent != null) { 3054 parcel.writeInt(1); 3055 fullScreenIntent.writeToParcel(parcel, 0); 3056 } else { 3057 parcel.writeInt(0); 3058 } 3059 3060 parcel.writeInt(priority); 3061 3062 parcel.writeString8(category); 3063 3064 parcel.writeString8(mGroupKey); 3065 3066 parcel.writeString8(mSortKey); 3067 3068 parcel.writeBundle(extras); // null ok 3069 3070 parcel.writeTypedArray(actions, 0); // null ok 3071 3072 if (bigContentView != null) { 3073 parcel.writeInt(1); 3074 bigContentView.writeToParcel(parcel, 0); 3075 } else { 3076 parcel.writeInt(0); 3077 } 3078 3079 if (headsUpContentView != null) { 3080 parcel.writeInt(1); 3081 headsUpContentView.writeToParcel(parcel, 0); 3082 } else { 3083 parcel.writeInt(0); 3084 } 3085 3086 parcel.writeInt(visibility); 3087 3088 if (publicVersion != null) { 3089 parcel.writeInt(1); 3090 publicVersion.writeToParcel(parcel, 0); 3091 } else { 3092 parcel.writeInt(0); 3093 } 3094 3095 parcel.writeInt(color); 3096 3097 if (mChannelId != null) { 3098 parcel.writeInt(1); 3099 parcel.writeString8(mChannelId); 3100 } else { 3101 parcel.writeInt(0); 3102 } 3103 parcel.writeLong(mTimeout); 3104 3105 if (mShortcutId != null) { 3106 parcel.writeInt(1); 3107 parcel.writeString8(mShortcutId); 3108 } else { 3109 parcel.writeInt(0); 3110 } 3111 3112 if (mLocusId != null) { 3113 parcel.writeInt(1); 3114 mLocusId.writeToParcel(parcel, 0); 3115 } else { 3116 parcel.writeInt(0); 3117 } 3118 3119 parcel.writeInt(mBadgeIcon); 3120 3121 if (mSettingsText != null) { 3122 parcel.writeInt(1); 3123 TextUtils.writeToParcel(mSettingsText, parcel, flags); 3124 } else { 3125 parcel.writeInt(0); 3126 } 3127 3128 parcel.writeInt(mGroupAlertBehavior); 3129 3130 if (mBubbleMetadata != null) { 3131 parcel.writeInt(1); 3132 mBubbleMetadata.writeToParcel(parcel, 0); 3133 } else { 3134 parcel.writeInt(0); 3135 } 3136 3137 parcel.writeBoolean(mAllowSystemGeneratedContextualActions); 3138 3139 parcel.writeInt(mFgsDeferBehavior); 3140 3141 // mUsesStandardHeader is not written because it should be recomputed in listeners 3142 } 3143 3144 /** 3145 * Parcelable.Creator that instantiates Notification objects 3146 */ 3147 public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR 3148 = new Parcelable.Creator<Notification>() 3149 { 3150 public Notification createFromParcel(Parcel parcel) 3151 { 3152 return new Notification(parcel); 3153 } 3154 3155 public Notification[] newArray(int size) 3156 { 3157 return new Notification[size]; 3158 } 3159 }; 3160 3161 /** 3162 * @hide 3163 */ areActionsVisiblyDifferent(Notification first, Notification second)3164 public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { 3165 Notification.Action[] firstAs = first.actions; 3166 Notification.Action[] secondAs = second.actions; 3167 if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { 3168 return true; 3169 } 3170 if (firstAs != null && secondAs != null) { 3171 if (firstAs.length != secondAs.length) { 3172 return true; 3173 } 3174 for (int i = 0; i < firstAs.length; i++) { 3175 if (!Objects.equals(String.valueOf(firstAs[i].title), 3176 String.valueOf(secondAs[i].title))) { 3177 return true; 3178 } 3179 RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); 3180 RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); 3181 if (firstRs == null) { 3182 firstRs = new RemoteInput[0]; 3183 } 3184 if (secondRs == null) { 3185 secondRs = new RemoteInput[0]; 3186 } 3187 if (firstRs.length != secondRs.length) { 3188 return true; 3189 } 3190 for (int j = 0; j < firstRs.length; j++) { 3191 if (!Objects.equals(String.valueOf(firstRs[j].getLabel()), 3192 String.valueOf(secondRs[j].getLabel()))) { 3193 return true; 3194 } 3195 } 3196 } 3197 } 3198 return false; 3199 } 3200 3201 /** 3202 * @hide 3203 */ areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3204 public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { 3205 if (first.getStyle() == null) { 3206 return second.getStyle() != null; 3207 } 3208 if (second.getStyle() == null) { 3209 return true; 3210 } 3211 return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); 3212 } 3213 3214 /** 3215 * @hide 3216 */ areRemoteViewsChanged(Builder first, Builder second)3217 public static boolean areRemoteViewsChanged(Builder first, Builder second) { 3218 if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) { 3219 return true; 3220 } 3221 3222 if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) { 3223 return true; 3224 } 3225 if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) { 3226 return true; 3227 } 3228 if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) { 3229 return true; 3230 } 3231 3232 return false; 3233 } 3234 areRemoteViewsChanged(RemoteViews first, RemoteViews second)3235 private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) { 3236 if (first == null && second == null) { 3237 return false; 3238 } 3239 if (first == null && second != null || first != null && second == null) { 3240 return true; 3241 } 3242 3243 if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) { 3244 return true; 3245 } 3246 3247 if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) { 3248 return true; 3249 } 3250 3251 return false; 3252 } 3253 3254 /** 3255 * Parcelling creates multiple copies of objects in {@code extras}. Fix them. 3256 * <p> 3257 * For backwards compatibility {@code extras} holds some references to "real" member data such 3258 * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly 3259 * fine as long as the object stays in one process. 3260 * <p> 3261 * However, once the notification goes into a parcel each reference gets marshalled separately, 3262 * wasting memory. Especially with large images on Auto and TV, this is worth fixing. 3263 */ fixDuplicateExtras()3264 private void fixDuplicateExtras() { 3265 if (extras != null) { 3266 fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); 3267 } 3268 } 3269 3270 /** 3271 * If we find an extra that's exactly the same as one of the "real" fields but refers to a 3272 * separate object, replace it with the field's version to avoid holding duplicate copies. 3273 */ fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3274 private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { 3275 if (original != null && extras.getParcelable(extraName) != null) { 3276 extras.putParcelable(extraName, original); 3277 } 3278 } 3279 3280 /** 3281 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 3282 * layout. 3283 * 3284 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 3285 * in the view.</p> 3286 * @param context The context for your application / activity. 3287 * @param contentTitle The title that goes in the expanded entry. 3288 * @param contentText The text that goes in the expanded entry. 3289 * @param contentIntent The intent to launch when the user clicks the expanded notification. 3290 * If this is an activity, it must include the 3291 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 3292 * that you take care of task management as described in the 3293 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 3294 * Stack</a> document. 3295 * 3296 * @deprecated Use {@link Builder} instead. 3297 * @removed 3298 */ 3299 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3300 public void setLatestEventInfo(Context context, 3301 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 3302 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 3303 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 3304 new Throwable()); 3305 } 3306 3307 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3308 extras.putBoolean(EXTRA_SHOW_WHEN, true); 3309 } 3310 3311 // ensure that any information already set directly is preserved 3312 final Notification.Builder builder = new Notification.Builder(context, this); 3313 3314 // now apply the latestEventInfo fields 3315 if (contentTitle != null) { 3316 builder.setContentTitle(contentTitle); 3317 } 3318 if (contentText != null) { 3319 builder.setContentText(contentText); 3320 } 3321 builder.setContentIntent(contentIntent); 3322 3323 builder.build(); // callers expect this notification to be ready to use 3324 } 3325 3326 /** 3327 * Sets the token used for background operations for the pending intents associated with this 3328 * notification. 3329 * 3330 * This token is automatically set during deserialization for you, you usually won't need to 3331 * call this unless you want to change the existing token, if any. 3332 * 3333 * @hide 3334 */ setAllowlistToken(@ullable IBinder token)3335 public void setAllowlistToken(@Nullable IBinder token) { 3336 mAllowlistToken = token; 3337 } 3338 3339 /** 3340 * @hide 3341 */ addFieldsFromContext(Context context, Notification notification)3342 public static void addFieldsFromContext(Context context, Notification notification) { 3343 addFieldsFromContext(context.getApplicationInfo(), notification); 3344 } 3345 3346 /** 3347 * @hide 3348 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)3349 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 3350 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 3351 } 3352 3353 /** 3354 * @hide 3355 */ dumpDebug(ProtoOutputStream proto, long fieldId)3356 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 3357 long token = proto.start(fieldId); 3358 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 3359 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 3360 proto.write(NotificationProto.FLAGS, this.flags); 3361 proto.write(NotificationProto.COLOR, this.color); 3362 proto.write(NotificationProto.CATEGORY, this.category); 3363 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 3364 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 3365 if (this.actions != null) { 3366 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 3367 } 3368 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 3369 proto.write(NotificationProto.VISIBILITY, this.visibility); 3370 } 3371 if (publicVersion != null) { 3372 publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION); 3373 } 3374 proto.end(token); 3375 } 3376 3377 @Override toString()3378 public String toString() { 3379 StringBuilder sb = new StringBuilder(); 3380 sb.append("Notification(channel="); 3381 sb.append(getChannelId()); 3382 sb.append(" shortcut="); 3383 sb.append(getShortcutId()); 3384 sb.append(" contentView="); 3385 if (contentView != null) { 3386 sb.append(contentView.getPackage()); 3387 sb.append("/0x"); 3388 sb.append(Integer.toHexString(contentView.getLayoutId())); 3389 } else { 3390 sb.append("null"); 3391 } 3392 sb.append(" vibrate="); 3393 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3394 sb.append("default"); 3395 } else if (this.vibrate != null) { 3396 int N = this.vibrate.length-1; 3397 sb.append("["); 3398 for (int i=0; i<N; i++) { 3399 sb.append(this.vibrate[i]); 3400 sb.append(','); 3401 } 3402 if (N != -1) { 3403 sb.append(this.vibrate[N]); 3404 } 3405 sb.append("]"); 3406 } else { 3407 sb.append("null"); 3408 } 3409 sb.append(" sound="); 3410 if ((this.defaults & DEFAULT_SOUND) != 0) { 3411 sb.append("default"); 3412 } else if (this.sound != null) { 3413 sb.append(this.sound.toString()); 3414 } else { 3415 sb.append("null"); 3416 } 3417 if (this.tickerText != null) { 3418 sb.append(" tick"); 3419 } 3420 sb.append(" defaults=0x"); 3421 sb.append(Integer.toHexString(this.defaults)); 3422 sb.append(" flags=0x"); 3423 sb.append(Integer.toHexString(this.flags)); 3424 sb.append(String.format(" color=0x%08x", this.color)); 3425 if (this.category != null) { 3426 sb.append(" category="); 3427 sb.append(this.category); 3428 } 3429 if (this.mGroupKey != null) { 3430 sb.append(" groupKey="); 3431 sb.append(this.mGroupKey); 3432 } 3433 if (this.mSortKey != null) { 3434 sb.append(" sortKey="); 3435 sb.append(this.mSortKey); 3436 } 3437 if (actions != null) { 3438 sb.append(" actions="); 3439 sb.append(actions.length); 3440 } 3441 sb.append(" vis="); 3442 sb.append(visibilityToString(this.visibility)); 3443 if (this.publicVersion != null) { 3444 sb.append(" publicVersion="); 3445 sb.append(publicVersion.toString()); 3446 } 3447 if (this.mLocusId != null) { 3448 sb.append(" locusId="); 3449 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3450 } 3451 sb.append(")"); 3452 return sb.toString(); 3453 } 3454 3455 /** 3456 * {@hide} 3457 */ visibilityToString(int vis)3458 public static String visibilityToString(int vis) { 3459 switch (vis) { 3460 case VISIBILITY_PRIVATE: 3461 return "PRIVATE"; 3462 case VISIBILITY_PUBLIC: 3463 return "PUBLIC"; 3464 case VISIBILITY_SECRET: 3465 return "SECRET"; 3466 default: 3467 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3468 } 3469 } 3470 3471 /** 3472 * {@hide} 3473 */ priorityToString(@riority int pri)3474 public static String priorityToString(@Priority int pri) { 3475 switch (pri) { 3476 case PRIORITY_MIN: 3477 return "MIN"; 3478 case PRIORITY_LOW: 3479 return "LOW"; 3480 case PRIORITY_DEFAULT: 3481 return "DEFAULT"; 3482 case PRIORITY_HIGH: 3483 return "HIGH"; 3484 case PRIORITY_MAX: 3485 return "MAX"; 3486 default: 3487 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3488 } 3489 } 3490 3491 /** 3492 * @hide 3493 */ hasCompletedProgress()3494 public boolean hasCompletedProgress() { 3495 // not a progress notification; can't be complete 3496 if (!extras.containsKey(EXTRA_PROGRESS) 3497 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 3498 return false; 3499 } 3500 // many apps use max 0 for 'indeterminate'; not complete 3501 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 3502 return false; 3503 } 3504 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 3505 } 3506 3507 /** @removed */ 3508 @Deprecated getChannel()3509 public String getChannel() { 3510 return mChannelId; 3511 } 3512 3513 /** 3514 * Returns the id of the channel this notification posts to. 3515 */ getChannelId()3516 public String getChannelId() { 3517 return mChannelId; 3518 } 3519 3520 /** @removed */ 3521 @Deprecated getTimeout()3522 public long getTimeout() { 3523 return mTimeout; 3524 } 3525 3526 /** 3527 * Returns the duration from posting after which this notification should be canceled by the 3528 * system, if it's not canceled already. 3529 */ getTimeoutAfter()3530 public long getTimeoutAfter() { 3531 return mTimeout; 3532 } 3533 3534 /** 3535 * Returns what icon should be shown for this notification if it is being displayed in a 3536 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 3537 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 3538 */ getBadgeIconType()3539 public int getBadgeIconType() { 3540 return mBadgeIcon; 3541 } 3542 3543 /** 3544 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 3545 * 3546 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 3547 * notifications. 3548 */ getShortcutId()3549 public String getShortcutId() { 3550 return mShortcutId; 3551 } 3552 3553 /** 3554 * Gets the {@link LocusId} associated with this notification. 3555 * 3556 * <p>Used by the device's intelligence services to correlate objects (such as 3557 * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated. 3558 */ 3559 @Nullable getLocusId()3560 public LocusId getLocusId() { 3561 return mLocusId; 3562 } 3563 3564 /** 3565 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 3566 */ getSettingsText()3567 public CharSequence getSettingsText() { 3568 return mSettingsText; 3569 } 3570 3571 /** 3572 * Returns which type of notifications in a group are responsible for audibly alerting the 3573 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 3574 * {@link #GROUP_ALERT_SUMMARY}. 3575 */ getGroupAlertBehavior()3576 public @GroupAlertBehavior int getGroupAlertBehavior() { 3577 return mGroupAlertBehavior; 3578 } 3579 3580 /** 3581 * Returns the bubble metadata that will be used to display app content in a floating window 3582 * over the existing foreground activity. 3583 */ 3584 @Nullable getBubbleMetadata()3585 public BubbleMetadata getBubbleMetadata() { 3586 return mBubbleMetadata; 3587 } 3588 3589 /** 3590 * Sets the {@link BubbleMetadata} for this notification. 3591 * @hide 3592 */ setBubbleMetadata(BubbleMetadata data)3593 public void setBubbleMetadata(BubbleMetadata data) { 3594 mBubbleMetadata = data; 3595 } 3596 3597 /** 3598 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 3599 * for this notification. 3600 */ getAllowSystemGeneratedContextualActions()3601 public boolean getAllowSystemGeneratedContextualActions() { 3602 return mAllowSystemGeneratedContextualActions; 3603 } 3604 3605 /** 3606 * The small icon representing this notification in the status bar and content view. 3607 * 3608 * @return the small icon representing this notification. 3609 * 3610 * @see Builder#getSmallIcon() 3611 * @see Builder#setSmallIcon(Icon) 3612 */ getSmallIcon()3613 public Icon getSmallIcon() { 3614 return mSmallIcon; 3615 } 3616 3617 /** 3618 * Used when notifying to clean up legacy small icons. 3619 * @hide 3620 */ 3621 @UnsupportedAppUsage setSmallIcon(Icon icon)3622 public void setSmallIcon(Icon icon) { 3623 mSmallIcon = icon; 3624 } 3625 3626 /** 3627 * The large icon shown in this notification's content view. 3628 * @see Builder#getLargeIcon() 3629 * @see Builder#setLargeIcon(Icon) 3630 */ getLargeIcon()3631 public Icon getLargeIcon() { 3632 return mLargeIcon; 3633 } 3634 3635 /** 3636 * @hide 3637 */ 3638 @UnsupportedAppUsage isGroupSummary()3639 public boolean isGroupSummary() { 3640 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 3641 } 3642 3643 /** 3644 * @hide 3645 */ 3646 @UnsupportedAppUsage isGroupChild()3647 public boolean isGroupChild() { 3648 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 3649 } 3650 3651 /** 3652 * @hide 3653 */ suppressAlertingDueToGrouping()3654 public boolean suppressAlertingDueToGrouping() { 3655 if (isGroupSummary() 3656 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 3657 return true; 3658 } else if (isGroupChild() 3659 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 3660 return true; 3661 } 3662 return false; 3663 } 3664 3665 3666 /** 3667 * Finds and returns a remote input and its corresponding action. 3668 * 3669 * @param requiresFreeform requires the remoteinput to allow freeform or not. 3670 * @return the result pair, {@code null} if no result is found. 3671 */ 3672 @Nullable findRemoteInputActionPair(boolean requiresFreeform)3673 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 3674 if (actions == null) { 3675 return null; 3676 } 3677 for (Notification.Action action : actions) { 3678 if (action.getRemoteInputs() == null) { 3679 continue; 3680 } 3681 RemoteInput resultRemoteInput = null; 3682 for (RemoteInput remoteInput : action.getRemoteInputs()) { 3683 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 3684 resultRemoteInput = remoteInput; 3685 } 3686 } 3687 if (resultRemoteInput != null) { 3688 return Pair.create(resultRemoteInput, action); 3689 } 3690 } 3691 return null; 3692 } 3693 3694 /** 3695 * Returns the actions that are contextual (that is, suggested because of the content of the 3696 * notification) out of the actions in this notification. 3697 */ getContextualActions()3698 public @NonNull List<Notification.Action> getContextualActions() { 3699 if (actions == null) return Collections.emptyList(); 3700 3701 List<Notification.Action> contextualActions = new ArrayList<>(); 3702 for (Notification.Action action : actions) { 3703 if (action.isContextual()) { 3704 contextualActions.add(action); 3705 } 3706 } 3707 return contextualActions; 3708 } 3709 3710 /** 3711 * Builder class for {@link Notification} objects. 3712 * 3713 * Provides a convenient way to set the various fields of a {@link Notification} and generate 3714 * content views using the platform's notification layout template. If your app supports 3715 * versions of Android as old as API level 4, you can instead use 3716 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 3717 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 3718 * library</a>. 3719 * 3720 * <p>Example: 3721 * 3722 * <pre class="prettyprint"> 3723 * Notification noti = new Notification.Builder(mContext) 3724 * .setContentTitle("New mail from " + sender.toString()) 3725 * .setContentText(subject) 3726 * .setSmallIcon(R.drawable.new_mail) 3727 * .setLargeIcon(aBitmap) 3728 * .build(); 3729 * </pre> 3730 */ 3731 public static class Builder { 3732 /** 3733 * @hide 3734 */ 3735 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 3736 "android.rebuild.contentViewActionCount"; 3737 /** 3738 * @hide 3739 */ 3740 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 3741 = "android.rebuild.bigViewActionCount"; 3742 /** 3743 * @hide 3744 */ 3745 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 3746 = "android.rebuild.hudViewActionCount"; 3747 3748 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 3749 SystemProperties.getBoolean("notifications.only_title", true); 3750 3751 /** 3752 * The lightness difference that has to be added to the primary text color to obtain the 3753 * secondary text color when the background is light. 3754 */ 3755 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 3756 3757 /** 3758 * The lightness difference that has to be added to the primary text color to obtain the 3759 * secondary text color when the background is dark. 3760 * A bit less then the above value, since it looks better on dark backgrounds. 3761 */ 3762 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 3763 3764 private Context mContext; 3765 private Notification mN; 3766 private Bundle mUserExtras = new Bundle(); 3767 private Style mStyle; 3768 @UnsupportedAppUsage 3769 private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS); 3770 private ArrayList<Person> mPersonList = new ArrayList<>(); 3771 private ContrastColorUtil mColorUtil; 3772 private boolean mIsLegacy; 3773 private boolean mIsLegacyInitialized; 3774 3775 /** 3776 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 3777 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 3778 */ 3779 StandardTemplateParams mParams = new StandardTemplateParams(); 3780 Colors mColors = new Colors(); 3781 3782 private boolean mTintActionButtons; 3783 private boolean mInNightMode; 3784 3785 /** 3786 * Constructs a new Builder with the defaults: 3787 * 3788 * @param context 3789 * A {@link Context} that will be used by the Builder to construct the 3790 * RemoteViews. The Context will not be held past the lifetime of this Builder 3791 * object. 3792 * @param channelId 3793 * The constructed Notification will be posted on this 3794 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 3795 * created using {@link NotificationManager#createNotificationChannel}. 3796 */ Builder(Context context, String channelId)3797 public Builder(Context context, String channelId) { 3798 this(context, (Notification) null); 3799 mN.mChannelId = channelId; 3800 } 3801 3802 /** 3803 * @deprecated use {@link #Builder(Context, String)} 3804 * instead. All posted Notifications must specify a NotificationChannel Id. 3805 */ 3806 @Deprecated Builder(Context context)3807 public Builder(Context context) { 3808 this(context, (Notification) null); 3809 } 3810 3811 /** 3812 * @hide 3813 */ Builder(Context context, Notification toAdopt)3814 public Builder(Context context, Notification toAdopt) { 3815 mContext = context; 3816 Resources res = mContext.getResources(); 3817 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 3818 3819 if (res.getBoolean(R.bool.config_enableNightMode)) { 3820 Configuration currentConfig = res.getConfiguration(); 3821 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 3822 == Configuration.UI_MODE_NIGHT_YES; 3823 } 3824 3825 if (toAdopt == null) { 3826 mN = new Notification(); 3827 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3828 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 3829 } 3830 mN.priority = PRIORITY_DEFAULT; 3831 mN.visibility = VISIBILITY_PRIVATE; 3832 } else { 3833 mN = toAdopt; 3834 if (mN.actions != null) { 3835 Collections.addAll(mActions, mN.actions); 3836 } 3837 3838 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 3839 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 3840 mPersonList.addAll(people); 3841 } 3842 3843 if (mN.getSmallIcon() == null && mN.icon != 0) { 3844 setSmallIcon(mN.icon); 3845 } 3846 3847 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 3848 setLargeIcon(mN.largeIcon); 3849 } 3850 3851 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 3852 if (!TextUtils.isEmpty(templateClass)) { 3853 final Class<? extends Style> styleClass 3854 = getNotificationStyleClass(templateClass); 3855 if (styleClass == null) { 3856 Log.d(TAG, "Unknown style class: " + templateClass); 3857 } else { 3858 try { 3859 final Constructor<? extends Style> ctor = 3860 styleClass.getDeclaredConstructor(); 3861 ctor.setAccessible(true); 3862 final Style style = ctor.newInstance(); 3863 style.restoreFromExtras(mN.extras); 3864 3865 if (style != null) { 3866 setStyle(style); 3867 } 3868 } catch (Throwable t) { 3869 Log.e(TAG, "Could not create Style", t); 3870 } 3871 } 3872 } 3873 } 3874 } 3875 getColorUtil()3876 private ContrastColorUtil getColorUtil() { 3877 if (mColorUtil == null) { 3878 mColorUtil = ContrastColorUtil.getInstance(mContext); 3879 } 3880 return mColorUtil; 3881 } 3882 3883 /** 3884 * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that 3885 * use this method to link to a published long-lived sharing shortcut may appear in a 3886 * dedicated Conversation section of the shade and may show configuration options that 3887 * are unique to conversations. This behavior should be reserved for person to person(s) 3888 * conversations where there is a likely social obligation for an individual to respond. 3889 * <p> 3890 * For example, the following are some examples of notifications that belong in the 3891 * conversation space: 3892 * <ul> 3893 * <li>1:1 conversations between two individuals</li> 3894 * <li>Group conversations between individuals where everyone can contribute</li> 3895 * </ul> 3896 * And the following are some examples of notifications that do not belong in the 3897 * conversation space: 3898 * <ul> 3899 * <li>Advertisements from a bot (even if personal and contextualized)</li> 3900 * <li>Engagement notifications from a bot</li> 3901 * <li>Directional conversations where there is an active speaker and many passive 3902 * individuals</li> 3903 * <li>Stream / posting updates from other individuals</li> 3904 * <li>Email, document comments, or other conversation types that are not real-time</li> 3905 * </ul> 3906 * </p> 3907 * 3908 * <p> 3909 * Additionally, this method can be used for all types of notifications to mark this 3910 * notification as duplicative of a Launcher shortcut. Launchers that show badges or 3911 * notification content may then suppress the shortcut in favor of the content of this 3912 * notification. 3913 * <p> 3914 * If this notification has {@link BubbleMetadata} attached that was created with 3915 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 3916 * metadata matches the shortcutId set here, if one was set. If the shortcutId's were 3917 * specified but do not match, an exception is thrown. 3918 * 3919 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 3920 * is linked to 3921 * 3922 * @see BubbleMetadata.Builder#Builder(String) 3923 */ 3924 @NonNull setShortcutId(String shortcutId)3925 public Builder setShortcutId(String shortcutId) { 3926 mN.mShortcutId = shortcutId; 3927 return this; 3928 } 3929 3930 /** 3931 * Sets the {@link LocusId} associated with this notification. 3932 * 3933 * <p>This method should be called when the {@link LocusId} is used in other places (such 3934 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence 3935 * services can correlate them. 3936 */ 3937 @NonNull setLocusId(@ullable LocusId locusId)3938 public Builder setLocusId(@Nullable LocusId locusId) { 3939 mN.mLocusId = locusId; 3940 return this; 3941 } 3942 3943 /** 3944 * Sets which icon to display as a badge for this notification. 3945 * 3946 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 3947 * {@link #BADGE_ICON_LARGE}. 3948 * 3949 * Note: This value might be ignored, for launchers that don't support badge icons. 3950 */ 3951 @NonNull setBadgeIconType(int icon)3952 public Builder setBadgeIconType(int icon) { 3953 mN.mBadgeIcon = icon; 3954 return this; 3955 } 3956 3957 /** 3958 * Sets the group alert behavior for this notification. Use this method to mute this 3959 * notification if alerts for this notification's group should be handled by a different 3960 * notification. This is only applicable for notifications that belong to a 3961 * {@link #setGroup(String) group}. This must be called on all notifications you want to 3962 * mute. For example, if you want only the summary of your group to make noise, all 3963 * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. 3964 * 3965 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 3966 */ 3967 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)3968 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 3969 mN.mGroupAlertBehavior = groupAlertBehavior; 3970 return this; 3971 } 3972 3973 /** 3974 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 3975 * window over the existing foreground activity. 3976 * 3977 * <p>This data will be ignored unless the notification is posted to a channel that 3978 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 3979 * 3980 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 3981 * collapsed state outside of the notification shade on unlocked devices. When a user 3982 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 3983 */ 3984 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)3985 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 3986 mN.mBubbleMetadata = data; 3987 return this; 3988 } 3989 3990 /** @removed */ 3991 @Deprecated setChannel(String channelId)3992 public Builder setChannel(String channelId) { 3993 mN.mChannelId = channelId; 3994 return this; 3995 } 3996 3997 /** 3998 * Specifies the channel the notification should be delivered on. 3999 */ 4000 @NonNull setChannelId(String channelId)4001 public Builder setChannelId(String channelId) { 4002 mN.mChannelId = channelId; 4003 return this; 4004 } 4005 4006 /** @removed */ 4007 @Deprecated setTimeout(long durationMs)4008 public Builder setTimeout(long durationMs) { 4009 mN.mTimeout = durationMs; 4010 return this; 4011 } 4012 4013 /** 4014 * Specifies a duration in milliseconds after which this notification should be canceled, 4015 * if it is not already canceled. 4016 */ 4017 @NonNull setTimeoutAfter(long durationMs)4018 public Builder setTimeoutAfter(long durationMs) { 4019 mN.mTimeout = durationMs; 4020 return this; 4021 } 4022 4023 /** 4024 * Add a timestamp pertaining to the notification (usually the time the event occurred). 4025 * 4026 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 4027 * shown anymore by default and must be opted into by using 4028 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 4029 * 4030 * @see Notification#when 4031 */ 4032 @NonNull setWhen(long when)4033 public Builder setWhen(long when) { 4034 mN.when = when; 4035 return this; 4036 } 4037 4038 /** 4039 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 4040 * in the content view. 4041 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 4042 * {@code false}. For earlier apps, the default is {@code true}. 4043 */ 4044 @NonNull setShowWhen(boolean show)4045 public Builder setShowWhen(boolean show) { 4046 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 4047 return this; 4048 } 4049 4050 /** 4051 * Show the {@link Notification#when} field as a stopwatch. 4052 * 4053 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 4054 * automatically updating display of the minutes and seconds since <code>when</code>. 4055 * 4056 * Useful when showing an elapsed time (like an ongoing phone call). 4057 * 4058 * The counter can also be set to count down to <code>when</code> when using 4059 * {@link #setChronometerCountDown(boolean)}. 4060 * 4061 * @see android.widget.Chronometer 4062 * @see Notification#when 4063 * @see #setChronometerCountDown(boolean) 4064 */ 4065 @NonNull setUsesChronometer(boolean b)4066 public Builder setUsesChronometer(boolean b) { 4067 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 4068 return this; 4069 } 4070 4071 /** 4072 * Sets the Chronometer to count down instead of counting up. 4073 * 4074 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 4075 * If it isn't set the chronometer will count up. 4076 * 4077 * @see #setUsesChronometer(boolean) 4078 */ 4079 @NonNull setChronometerCountDown(boolean countDown)4080 public Builder setChronometerCountDown(boolean countDown) { 4081 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 4082 return this; 4083 } 4084 4085 /** 4086 * Set the small icon resource, which will be used to represent the notification in the 4087 * status bar. 4088 * 4089 4090 * The platform template for the expanded view will draw this icon in the left, unless a 4091 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 4092 * icon will be moved to the right-hand side. 4093 * 4094 4095 * @param icon 4096 * A resource ID in the application's package of the drawable to use. 4097 * @see Notification#icon 4098 */ 4099 @NonNull setSmallIcon(@rawableRes int icon)4100 public Builder setSmallIcon(@DrawableRes int icon) { 4101 return setSmallIcon(icon != 0 4102 ? Icon.createWithResource(mContext, icon) 4103 : null); 4104 } 4105 4106 /** 4107 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 4108 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 4109 * LevelListDrawable}. 4110 * 4111 * @param icon A resource ID in the application's package of the drawable to use. 4112 * @param level The level to use for the icon. 4113 * 4114 * @see Notification#icon 4115 * @see Notification#iconLevel 4116 */ 4117 @NonNull setSmallIcon(@rawableRes int icon, int level)4118 public Builder setSmallIcon(@DrawableRes int icon, int level) { 4119 mN.iconLevel = level; 4120 return setSmallIcon(icon); 4121 } 4122 4123 /** 4124 * Set the small icon, which will be used to represent the notification in the 4125 * status bar and content view (unless overridden there by a 4126 * {@link #setLargeIcon(Bitmap) large icon}). 4127 * 4128 * @param icon An Icon object to use. 4129 * @see Notification#icon 4130 */ 4131 @NonNull setSmallIcon(Icon icon)4132 public Builder setSmallIcon(Icon icon) { 4133 mN.setSmallIcon(icon); 4134 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 4135 mN.icon = icon.getResId(); 4136 } 4137 return this; 4138 } 4139 4140 /** 4141 * Set the first line of text in the platform notification template. 4142 */ 4143 @NonNull setContentTitle(CharSequence title)4144 public Builder setContentTitle(CharSequence title) { 4145 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 4146 return this; 4147 } 4148 4149 /** 4150 * Set the second line of text in the platform notification template. 4151 */ 4152 @NonNull setContentText(CharSequence text)4153 public Builder setContentText(CharSequence text) { 4154 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 4155 return this; 4156 } 4157 4158 /** 4159 * This provides some additional information that is displayed in the notification. No 4160 * guarantees are given where exactly it is displayed. 4161 * 4162 * <p>This information should only be provided if it provides an essential 4163 * benefit to the understanding of the notification. The more text you provide the 4164 * less readable it becomes. For example, an email client should only provide the account 4165 * name here if more than one email account has been added.</p> 4166 * 4167 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 4168 * notification header area. 4169 * 4170 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 4171 * this will be shown in the third line of text in the platform notification template. 4172 * You should not be using {@link #setProgress(int, int, boolean)} at the 4173 * same time on those versions; they occupy the same place. 4174 * </p> 4175 */ 4176 @NonNull setSubText(CharSequence text)4177 public Builder setSubText(CharSequence text) { 4178 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 4179 return this; 4180 } 4181 4182 /** 4183 * Provides text that will appear as a link to your application's settings. 4184 * 4185 * <p>This text does not appear within notification {@link Style templates} but may 4186 * appear when the user uses an affordance to learn more about the notification. 4187 * Additionally, this text will not appear unless you provide a valid link target by 4188 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 4189 * 4190 * <p>This text is meant to be concise description about what the user can customize 4191 * when they click on this link. The recommended maximum length is 40 characters. 4192 * @param text 4193 * @return 4194 */ 4195 @NonNull setSettingsText(CharSequence text)4196 public Builder setSettingsText(CharSequence text) { 4197 mN.mSettingsText = safeCharSequence(text); 4198 return this; 4199 } 4200 4201 /** 4202 * Set the remote input history. 4203 * 4204 * This should be set to the most recent inputs that have been sent 4205 * through a {@link RemoteInput} of this Notification and cleared once the it is no 4206 * longer relevant (e.g. for chat notifications once the other party has responded). 4207 * 4208 * The most recent input must be stored at the 0 index, the second most recent at the 4209 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 4210 * and how much of each individual input is shown. 4211 * 4212 * <p>Note: The reply text will only be shown on notifications that have least one action 4213 * with a {@code RemoteInput}.</p> 4214 */ 4215 @NonNull setRemoteInputHistory(CharSequence[] text)4216 public Builder setRemoteInputHistory(CharSequence[] text) { 4217 if (text == null) { 4218 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 4219 } else { 4220 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); 4221 CharSequence[] safe = new CharSequence[itemCount]; 4222 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; 4223 for (int i = 0; i < itemCount; i++) { 4224 safe[i] = safeCharSequence(text[i]); 4225 items[i] = new RemoteInputHistoryItem(text[i]); 4226 } 4227 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 4228 4229 // Also add these messages as structured history items. 4230 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); 4231 } 4232 return this; 4233 } 4234 4235 /** 4236 * Set the remote input history, with support for embedding URIs and mime types for 4237 * images and other media. 4238 * @hide 4239 */ 4240 @NonNull setRemoteInputHistory(RemoteInputHistoryItem[] items)4241 public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { 4242 if (items == null) { 4243 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); 4244 } else { 4245 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); 4246 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; 4247 for (int i = 0; i < itemCount; i++) { 4248 history[i] = items[i]; 4249 } 4250 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); 4251 } 4252 return this; 4253 } 4254 4255 /** 4256 * Sets whether remote history entries view should have a spinner. 4257 * @hide 4258 */ 4259 @NonNull setShowRemoteInputSpinner(boolean showSpinner)4260 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 4261 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 4262 return this; 4263 } 4264 4265 /** 4266 * Sets whether smart reply buttons should be hidden. 4267 * @hide 4268 */ 4269 @NonNull setHideSmartReplies(boolean hideSmartReplies)4270 public Builder setHideSmartReplies(boolean hideSmartReplies) { 4271 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 4272 return this; 4273 } 4274 4275 /** 4276 * Sets the number of items this notification represents. May be displayed as a badge count 4277 * for Launchers that support badging. 4278 */ 4279 @NonNull setNumber(int number)4280 public Builder setNumber(int number) { 4281 mN.number = number; 4282 return this; 4283 } 4284 4285 /** 4286 * A small piece of additional information pertaining to this notification. 4287 * 4288 * The platform template will draw this on the last line of the notification, at the far 4289 * right (to the right of a smallIcon if it has been placed there). 4290 * 4291 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 4292 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 4293 * field will still show up, but the subtext will take precedence. 4294 */ 4295 @Deprecated setContentInfo(CharSequence info)4296 public Builder setContentInfo(CharSequence info) { 4297 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 4298 return this; 4299 } 4300 4301 /** 4302 * Set the progress this notification represents. 4303 * 4304 * The platform template will represent this using a {@link ProgressBar}. 4305 */ 4306 @NonNull setProgress(int max, int progress, boolean indeterminate)4307 public Builder setProgress(int max, int progress, boolean indeterminate) { 4308 mN.extras.putInt(EXTRA_PROGRESS, progress); 4309 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 4310 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 4311 return this; 4312 } 4313 4314 /** 4315 * Supply a custom RemoteViews to use instead of the platform template. 4316 * 4317 * Use {@link #setCustomContentView(RemoteViews)} instead. 4318 */ 4319 @Deprecated setContent(RemoteViews views)4320 public Builder setContent(RemoteViews views) { 4321 return setCustomContentView(views); 4322 } 4323 4324 /** 4325 * Supply custom RemoteViews to use instead of the platform template. 4326 * 4327 * This will override the layout that would otherwise be constructed by this Builder 4328 * object. 4329 */ 4330 @NonNull setCustomContentView(RemoteViews contentView)4331 public Builder setCustomContentView(RemoteViews contentView) { 4332 mN.contentView = contentView; 4333 return this; 4334 } 4335 4336 /** 4337 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 4338 * 4339 * This will override the expanded layout that would otherwise be constructed by this 4340 * Builder object. 4341 */ 4342 @NonNull setCustomBigContentView(RemoteViews contentView)4343 public Builder setCustomBigContentView(RemoteViews contentView) { 4344 mN.bigContentView = contentView; 4345 return this; 4346 } 4347 4348 /** 4349 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 4350 * 4351 * This will override the heads-up layout that would otherwise be constructed by this 4352 * Builder object. 4353 */ 4354 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)4355 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 4356 mN.headsUpContentView = contentView; 4357 return this; 4358 } 4359 4360 /** 4361 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 4362 * 4363 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4364 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4365 * while processing broadcast receivers or services in response to notification clicks. To 4366 * launch an activity in those cases, provide a {@link PendingIntent} for the activity 4367 * itself. 4368 * 4369 * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 4370 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 4371 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 4372 * to assign PendingIntents to individual views in that custom layout (i.e., to create 4373 * clickable buttons inside the notification view). 4374 * 4375 * @see Notification#contentIntent Notification.contentIntent 4376 */ 4377 @NonNull setContentIntent(PendingIntent intent)4378 public Builder setContentIntent(PendingIntent intent) { 4379 mN.contentIntent = intent; 4380 return this; 4381 } 4382 4383 /** 4384 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 4385 * 4386 * @see Notification#deleteIntent 4387 */ 4388 @NonNull setDeleteIntent(PendingIntent intent)4389 public Builder setDeleteIntent(PendingIntent intent) { 4390 mN.deleteIntent = intent; 4391 return this; 4392 } 4393 4394 /** 4395 * An intent to launch instead of posting the notification to the status bar. 4396 * Only for use with extremely high-priority notifications demanding the user's 4397 * <strong>immediate</strong> attention, such as an incoming phone call or 4398 * alarm clock that the user has explicitly set to a particular time. 4399 * If this facility is used for something else, please give the user an option 4400 * to turn it off and use a normal notification, as this can be extremely 4401 * disruptive. 4402 * 4403 * <p> 4404 * The system UI may choose to display a heads-up notification, instead of 4405 * launching this intent, while the user is using the device. 4406 * </p> 4407 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 4408 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 4409 * use full screen intents.</p> 4410 * 4411 * @param intent The pending intent to launch. 4412 * @param highPriority Passing true will cause this notification to be sent 4413 * even if other notifications are suppressed. 4414 * 4415 * @see Notification#fullScreenIntent 4416 */ 4417 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)4418 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 4419 mN.fullScreenIntent = intent; 4420 setFlag(FLAG_HIGH_PRIORITY, highPriority); 4421 return this; 4422 } 4423 4424 /** 4425 * Set the "ticker" text which is sent to accessibility services. 4426 * 4427 * @see Notification#tickerText 4428 */ 4429 @NonNull setTicker(CharSequence tickerText)4430 public Builder setTicker(CharSequence tickerText) { 4431 mN.tickerText = safeCharSequence(tickerText); 4432 return this; 4433 } 4434 4435 /** 4436 * Obsolete version of {@link #setTicker(CharSequence)}. 4437 * 4438 */ 4439 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)4440 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 4441 setTicker(tickerText); 4442 // views is ignored 4443 return this; 4444 } 4445 4446 /** 4447 * Add a large icon to the notification content view. 4448 * 4449 * In the platform template, this image will be shown either on the right of the 4450 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4451 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4452 */ 4453 @NonNull setLargeIcon(Bitmap b)4454 public Builder setLargeIcon(Bitmap b) { 4455 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4456 } 4457 4458 /** 4459 * Add a large icon to the notification content view. 4460 * 4461 * In the platform template, this image will be shown either on the right of the 4462 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4463 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4464 */ 4465 @NonNull setLargeIcon(Icon icon)4466 public Builder setLargeIcon(Icon icon) { 4467 mN.mLargeIcon = icon; 4468 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 4469 return this; 4470 } 4471 4472 /** 4473 * Set the sound to play. 4474 * 4475 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 4476 * for notifications. 4477 * 4478 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4479 */ 4480 @Deprecated setSound(Uri sound)4481 public Builder setSound(Uri sound) { 4482 mN.sound = sound; 4483 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 4484 return this; 4485 } 4486 4487 /** 4488 * Set the sound to play, along with a specific stream on which to play it. 4489 * 4490 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 4491 * 4492 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 4493 */ 4494 @Deprecated setSound(Uri sound, int streamType)4495 public Builder setSound(Uri sound, int streamType) { 4496 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 4497 mN.sound = sound; 4498 mN.audioStreamType = streamType; 4499 return this; 4500 } 4501 4502 /** 4503 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 4504 * use during playback. 4505 * 4506 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4507 * @see Notification#sound 4508 */ 4509 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)4510 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 4511 mN.sound = sound; 4512 mN.audioAttributes = audioAttributes; 4513 return this; 4514 } 4515 4516 /** 4517 * Set the vibration pattern to use. 4518 * 4519 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 4520 * <code>pattern</code> parameter. 4521 * 4522 * <p> 4523 * A notification that vibrates is more likely to be presented as a heads-up notification. 4524 * </p> 4525 * 4526 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 4527 * @see Notification#vibrate 4528 */ 4529 @Deprecated setVibrate(long[] pattern)4530 public Builder setVibrate(long[] pattern) { 4531 mN.vibrate = pattern; 4532 return this; 4533 } 4534 4535 /** 4536 * Set the desired color for the indicator LED on the device, as well as the 4537 * blink duty cycle (specified in milliseconds). 4538 * 4539 4540 * Not all devices will honor all (or even any) of these values. 4541 * 4542 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 4543 * @see Notification#ledARGB 4544 * @see Notification#ledOnMS 4545 * @see Notification#ledOffMS 4546 */ 4547 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)4548 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 4549 mN.ledARGB = argb; 4550 mN.ledOnMS = onMs; 4551 mN.ledOffMS = offMs; 4552 if (onMs != 0 || offMs != 0) { 4553 mN.flags |= FLAG_SHOW_LIGHTS; 4554 } 4555 return this; 4556 } 4557 4558 /** 4559 * Set whether this is an "ongoing" notification. 4560 * 4561 4562 * Ongoing notifications cannot be dismissed by the user, so your application or service 4563 * must take care of canceling them. 4564 * 4565 4566 * They are typically used to indicate a background task that the user is actively engaged 4567 * with (e.g., playing music) or is pending in some way and therefore occupying the device 4568 * (e.g., a file download, sync operation, active network connection). 4569 * 4570 4571 * @see Notification#FLAG_ONGOING_EVENT 4572 */ 4573 @NonNull setOngoing(boolean ongoing)4574 public Builder setOngoing(boolean ongoing) { 4575 setFlag(FLAG_ONGOING_EVENT, ongoing); 4576 return this; 4577 } 4578 4579 /** 4580 * Set whether this notification should be colorized. When set, the color set with 4581 * {@link #setColor(int)} will be used as the background color of this notification. 4582 * <p> 4583 * This should only be used for high priority ongoing tasks like navigation, an ongoing 4584 * call, or other similarly high-priority events for the user. 4585 * <p> 4586 * For most styles, the coloring will only be applied if the notification is for a 4587 * foreground service notification. 4588 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 4589 * that have a media session attached there is no such requirement. 4590 * 4591 * @see #setColor(int) 4592 * @see MediaStyle#setMediaSession(MediaSession.Token) 4593 */ 4594 @NonNull setColorized(boolean colorize)4595 public Builder setColorized(boolean colorize) { 4596 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 4597 return this; 4598 } 4599 4600 /** 4601 * Set this flag if you would only like the sound, vibrate 4602 * and ticker to be played if the notification is not already showing. 4603 * 4604 * @see Notification#FLAG_ONLY_ALERT_ONCE 4605 */ 4606 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)4607 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 4608 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 4609 return this; 4610 } 4611 4612 /** 4613 * Specify a desired visibility policy for a Notification associated with a 4614 * foreground service. By default, the system can choose to defer 4615 * visibility of the notification for a short time after the service is 4616 * started. Pass 4617 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE} 4618 * to this method in order to guarantee that visibility is never deferred. Pass 4619 * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4620 * to request that visibility is deferred whenever possible. 4621 * 4622 * <p class="note">Note that deferred visibility is not guaranteed. There 4623 * may be some circumstances under which the system will show the foreground 4624 * service's associated Notification immediately even when the app has used 4625 * this method to explicitly request deferred display.</p> 4626 * @param behavior One of 4627 * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT}, 4628 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}, 4629 * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4630 * @return 4631 */ 4632 @NonNull setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4633 public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) { 4634 mN.mFgsDeferBehavior = behavior; 4635 return this; 4636 } 4637 4638 /** 4639 * Make this notification automatically dismissed when the user touches it. 4640 * 4641 * @see Notification#FLAG_AUTO_CANCEL 4642 */ 4643 @NonNull setAutoCancel(boolean autoCancel)4644 public Builder setAutoCancel(boolean autoCancel) { 4645 setFlag(FLAG_AUTO_CANCEL, autoCancel); 4646 return this; 4647 } 4648 4649 /** 4650 * Set whether or not this notification should not bridge to other devices. 4651 * 4652 * <p>Some notifications can be bridged to other devices for remote display. 4653 * This hint can be set to recommend this notification not be bridged. 4654 */ 4655 @NonNull setLocalOnly(boolean localOnly)4656 public Builder setLocalOnly(boolean localOnly) { 4657 setFlag(FLAG_LOCAL_ONLY, localOnly); 4658 return this; 4659 } 4660 4661 /** 4662 * Set which notification properties will be inherited from system defaults. 4663 * <p> 4664 * The value should be one or more of the following fields combined with 4665 * bitwise-or: 4666 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 4667 * <p> 4668 * For all default values, use {@link #DEFAULT_ALL}. 4669 * 4670 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 4671 * {@link NotificationChannel#enableLights(boolean)} and 4672 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4673 */ 4674 @Deprecated setDefaults(int defaults)4675 public Builder setDefaults(int defaults) { 4676 mN.defaults = defaults; 4677 return this; 4678 } 4679 4680 /** 4681 * Set the priority of this notification. 4682 * 4683 * @see Notification#priority 4684 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 4685 */ 4686 @Deprecated setPriority(@riority int pri)4687 public Builder setPriority(@Priority int pri) { 4688 mN.priority = pri; 4689 return this; 4690 } 4691 4692 /** 4693 * Set the notification category. 4694 * 4695 * @see Notification#category 4696 */ 4697 @NonNull setCategory(String category)4698 public Builder setCategory(String category) { 4699 mN.category = category; 4700 return this; 4701 } 4702 4703 /** 4704 * Add a person that is relevant to this notification. 4705 * 4706 * <P> 4707 * Depending on user preferences, this annotation may allow the notification to pass 4708 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4709 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4710 * appear more prominently in the user interface. 4711 * </P> 4712 * 4713 * <P> 4714 * The person should be specified by the {@code String} representation of a 4715 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 4716 * </P> 4717 * 4718 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 4719 * URIs. The path part of these URIs must exist in the contacts database, in the 4720 * appropriate column, or the reference will be discarded as invalid. Telephone schema 4721 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 4722 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 4723 * identify a person without an entry in the contacts database. 4724 * </P> 4725 * 4726 * @param uri A URI for the person. 4727 * @see Notification#EXTRA_PEOPLE 4728 * @deprecated use {@link #addPerson(Person)} 4729 */ addPerson(String uri)4730 public Builder addPerson(String uri) { 4731 addPerson(new Person.Builder().setUri(uri).build()); 4732 return this; 4733 } 4734 4735 /** 4736 * Add a person that is relevant to this notification. 4737 * 4738 * <P> 4739 * Depending on user preferences, this annotation may allow the notification to pass 4740 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4741 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4742 * appear more prominently in the user interface. 4743 * </P> 4744 * 4745 * <P> 4746 * A person should usually contain a uri in order to benefit from the ranking boost. 4747 * However, even if no uri is provided, it's beneficial to provide other people in the 4748 * notification, such that listeners and voice only devices can announce and handle them 4749 * properly. 4750 * </P> 4751 * 4752 * @param person the person to add. 4753 * @see Notification#EXTRA_PEOPLE_LIST 4754 */ 4755 @NonNull addPerson(Person person)4756 public Builder addPerson(Person person) { 4757 mPersonList.add(person); 4758 return this; 4759 } 4760 4761 /** 4762 * Set this notification to be part of a group of notifications sharing the same key. 4763 * Grouped notifications may display in a cluster or stack on devices which 4764 * support such rendering. 4765 * 4766 * <p>To make this notification the summary for its group, also call 4767 * {@link #setGroupSummary}. A sort order can be specified for group members by using 4768 * {@link #setSortKey}. 4769 * @param groupKey The group key of the group. 4770 * @return this object for method chaining 4771 */ 4772 @NonNull setGroup(String groupKey)4773 public Builder setGroup(String groupKey) { 4774 mN.mGroupKey = groupKey; 4775 return this; 4776 } 4777 4778 /** 4779 * Set this notification to be the group summary for a group of notifications. 4780 * Grouped notifications may display in a cluster or stack on devices which 4781 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 4782 * The group summary may be suppressed if too few notifications are included in the group. 4783 * @param isGroupSummary Whether this notification should be a group summary. 4784 * @return this object for method chaining 4785 */ 4786 @NonNull setGroupSummary(boolean isGroupSummary)4787 public Builder setGroupSummary(boolean isGroupSummary) { 4788 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 4789 return this; 4790 } 4791 4792 /** 4793 * Set a sort key that orders this notification among other notifications from the 4794 * same package. This can be useful if an external sort was already applied and an app 4795 * would like to preserve this. Notifications will be sorted lexicographically using this 4796 * value, although providing different priorities in addition to providing sort key may 4797 * cause this value to be ignored. 4798 * 4799 * <p>This sort key can also be used to order members of a notification group. See 4800 * {@link #setGroup}. 4801 * 4802 * @see String#compareTo(String) 4803 */ 4804 @NonNull setSortKey(String sortKey)4805 public Builder setSortKey(String sortKey) { 4806 mN.mSortKey = sortKey; 4807 return this; 4808 } 4809 4810 /** 4811 * Merge additional metadata into this notification. 4812 * 4813 * <p>Values within the Bundle will replace existing extras values in this Builder. 4814 * 4815 * @see Notification#extras 4816 */ 4817 @NonNull addExtras(Bundle extras)4818 public Builder addExtras(Bundle extras) { 4819 if (extras != null) { 4820 mUserExtras.putAll(extras); 4821 } 4822 return this; 4823 } 4824 4825 /** 4826 * Set metadata for this notification. 4827 * 4828 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 4829 * current contents are copied into the Notification each time {@link #build()} is 4830 * called. 4831 * 4832 * <p>Replaces any existing extras values with those from the provided Bundle. 4833 * Use {@link #addExtras} to merge in metadata instead. 4834 * 4835 * @see Notification#extras 4836 */ 4837 @NonNull setExtras(Bundle extras)4838 public Builder setExtras(Bundle extras) { 4839 if (extras != null) { 4840 mUserExtras = extras; 4841 } 4842 return this; 4843 } 4844 4845 /** 4846 * Get the current metadata Bundle used by this notification Builder. 4847 * 4848 * <p>The returned Bundle is shared with this Builder. 4849 * 4850 * <p>The current contents of this Bundle are copied into the Notification each time 4851 * {@link #build()} is called. 4852 * 4853 * @see Notification#extras 4854 */ getExtras()4855 public Bundle getExtras() { 4856 return mUserExtras; 4857 } 4858 getAllExtras()4859 private Bundle getAllExtras() { 4860 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 4861 saveExtras.putAll(mN.extras); 4862 return saveExtras; 4863 } 4864 4865 /** 4866 * Add an action to this notification. Actions are typically displayed by 4867 * the system as a button adjacent to the notification content. 4868 * <p> 4869 * Every action must have an icon (32dp square and matching the 4870 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4871 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4872 * <p> 4873 * A notification in its expanded form can display up to 3 actions, from left to right in 4874 * the order they were added. Actions will not be displayed when the notification is 4875 * collapsed, however, so be sure that any essential functions may be accessed by the user 4876 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4877 * <p> 4878 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4879 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4880 * while processing broadcast receivers or services in response to notification action 4881 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the 4882 * activity itself. 4883 * 4884 * @param icon Resource ID of a drawable that represents the action. 4885 * @param title Text describing the action. 4886 * @param intent PendingIntent to be fired when the action is invoked. 4887 * 4888 * @deprecated Use {@link #addAction(Action)} instead. 4889 */ 4890 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)4891 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 4892 mActions.add(new Action(icon, safeCharSequence(title), intent)); 4893 return this; 4894 } 4895 4896 /** 4897 * Add an action to this notification. Actions are typically displayed by 4898 * the system as a button adjacent to the notification content. 4899 * <p> 4900 * Every action must have an icon (32dp square and matching the 4901 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4902 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4903 * <p> 4904 * A notification in its expanded form can display up to 3 actions, from left to right in 4905 * the order they were added. Actions will not be displayed when the notification is 4906 * collapsed, however, so be sure that any essential functions may be accessed by the user 4907 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4908 * 4909 * @param action The action to add. 4910 */ 4911 @NonNull addAction(Action action)4912 public Builder addAction(Action action) { 4913 if (action != null) { 4914 mActions.add(action); 4915 } 4916 return this; 4917 } 4918 4919 /** 4920 * Alter the complete list of actions attached to this notification. 4921 * @see #addAction(Action). 4922 * 4923 * @param actions 4924 * @return 4925 */ 4926 @NonNull setActions(Action... actions)4927 public Builder setActions(Action... actions) { 4928 mActions.clear(); 4929 for (int i = 0; i < actions.length; i++) { 4930 if (actions[i] != null) { 4931 mActions.add(actions[i]); 4932 } 4933 } 4934 return this; 4935 } 4936 4937 /** 4938 * Add a rich notification style to be applied at build time. 4939 * 4940 * @param style Object responsible for modifying the notification style. 4941 */ 4942 @NonNull setStyle(Style style)4943 public Builder setStyle(Style style) { 4944 if (mStyle != style) { 4945 mStyle = style; 4946 if (mStyle != null) { 4947 mStyle.setBuilder(this); 4948 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 4949 } else { 4950 mN.extras.remove(EXTRA_TEMPLATE); 4951 } 4952 } 4953 return this; 4954 } 4955 4956 /** 4957 * Returns the style set by {@link #setStyle(Style)}. 4958 */ getStyle()4959 public Style getStyle() { 4960 return mStyle; 4961 } 4962 4963 /** 4964 * Specify the value of {@link #visibility}. 4965 * 4966 * @return The same Builder. 4967 */ 4968 @NonNull setVisibility(@isibility int visibility)4969 public Builder setVisibility(@Visibility int visibility) { 4970 mN.visibility = visibility; 4971 return this; 4972 } 4973 4974 /** 4975 * Supply a replacement Notification whose contents should be shown in insecure contexts 4976 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 4977 * @param n A replacement notification, presumably with some or all info redacted. 4978 * @return The same Builder. 4979 */ 4980 @NonNull setPublicVersion(Notification n)4981 public Builder setPublicVersion(Notification n) { 4982 if (n != null) { 4983 mN.publicVersion = new Notification(); 4984 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 4985 } else { 4986 mN.publicVersion = null; 4987 } 4988 return this; 4989 } 4990 4991 /** 4992 * Apply an extender to this notification builder. Extenders may be used to add 4993 * metadata or change options on this builder. 4994 */ 4995 @NonNull extend(Extender extender)4996 public Builder extend(Extender extender) { 4997 extender.extend(this); 4998 return this; 4999 } 5000 5001 /** 5002 * Set the value for a notification flag 5003 * 5004 * @param mask Bit mask of the flag 5005 * @param value Status (on/off) of the flag 5006 * 5007 * @return The same Builder. 5008 */ 5009 @NonNull setFlag(@otificationFlags int mask, boolean value)5010 public Builder setFlag(@NotificationFlags int mask, boolean value) { 5011 if (value) { 5012 mN.flags |= mask; 5013 } else { 5014 mN.flags &= ~mask; 5015 } 5016 return this; 5017 } 5018 5019 /** 5020 * Sets {@link Notification#color}. 5021 * 5022 * @param argb The accent color to use 5023 * 5024 * @return The same Builder. 5025 */ 5026 @NonNull setColor(@olorInt int argb)5027 public Builder setColor(@ColorInt int argb) { 5028 mN.color = argb; 5029 sanitizeColor(); 5030 return this; 5031 } 5032 bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5033 private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { 5034 contentView.setDrawableTint( 5035 R.id.phishing_alert, 5036 false /* targetBackground */, 5037 getColors(p).getErrorColor(), 5038 PorterDuff.Mode.SRC_ATOP); 5039 } 5040 getProfileBadgeDrawable()5041 private Drawable getProfileBadgeDrawable() { 5042 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 5043 // This user can never be a badged profile, 5044 // and also includes USER_ALL system notifications. 5045 return null; 5046 } 5047 // Note: This assumes that the current user can read the profile badge of the 5048 // originating user. 5049 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 5050 new UserHandle(mContext.getUserId()), 0); 5051 } 5052 getProfileBadge()5053 private Bitmap getProfileBadge() { 5054 Drawable badge = getProfileBadgeDrawable(); 5055 if (badge == null) { 5056 return null; 5057 } 5058 final int size = mContext.getResources().getDimensionPixelSize( 5059 R.dimen.notification_badge_size); 5060 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 5061 Canvas canvas = new Canvas(bitmap); 5062 badge.setBounds(0, 0, size, size); 5063 badge.draw(canvas); 5064 return bitmap; 5065 } 5066 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5067 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 5068 Bitmap profileBadge = getProfileBadge(); 5069 5070 if (profileBadge != null) { 5071 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 5072 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 5073 if (isBackgroundColorized(p)) { 5074 contentView.setDrawableTint(R.id.profile_badge, false, 5075 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 5076 } 5077 } 5078 } 5079 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5080 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 5081 contentView.setDrawableTint( 5082 R.id.alerted_icon, 5083 false /* targetBackground */, 5084 getColors(p).getSecondaryTextColor(), 5085 PorterDuff.Mode.SRC_IN); 5086 } 5087 5088 /** 5089 * @hide 5090 */ usesStandardHeader()5091 public boolean usesStandardHeader() { 5092 if (mN.mUsesStandardHeader) { 5093 return true; 5094 } 5095 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 5096 if (mN.contentView == null && mN.bigContentView == null) { 5097 return true; 5098 } 5099 } 5100 boolean contentViewUsesHeader = mN.contentView == null 5101 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 5102 boolean bigContentViewUsesHeader = mN.bigContentView == null 5103 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 5104 return contentViewUsesHeader && bigContentViewUsesHeader; 5105 } 5106 resetStandardTemplate(RemoteViews contentView)5107 private void resetStandardTemplate(RemoteViews contentView) { 5108 resetNotificationHeader(contentView); 5109 contentView.setViewVisibility(R.id.right_icon, View.GONE); 5110 contentView.setViewVisibility(R.id.title, View.GONE); 5111 contentView.setTextViewText(R.id.title, null); 5112 contentView.setViewVisibility(R.id.text, View.GONE); 5113 contentView.setTextViewText(R.id.text, null); 5114 } 5115 5116 /** 5117 * Resets the notification header to its original state 5118 */ resetNotificationHeader(RemoteViews contentView)5119 private void resetNotificationHeader(RemoteViews contentView) { 5120 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 5121 // re-using the drawable when the notification is updated. 5122 contentView.setBoolean(R.id.expand_button, "setExpanded", false); 5123 contentView.setViewVisibility(R.id.app_name_text, View.GONE); 5124 contentView.setTextViewText(R.id.app_name_text, null); 5125 contentView.setViewVisibility(R.id.chronometer, View.GONE); 5126 contentView.setViewVisibility(R.id.header_text, View.GONE); 5127 contentView.setTextViewText(R.id.header_text, null); 5128 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 5129 contentView.setTextViewText(R.id.header_text_secondary, null); 5130 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 5131 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 5132 contentView.setViewVisibility(R.id.time_divider, View.GONE); 5133 contentView.setViewVisibility(R.id.time, View.GONE); 5134 contentView.setImageViewIcon(R.id.profile_badge, null); 5135 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 5136 mN.mUsesStandardHeader = false; 5137 } 5138 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5139 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 5140 TemplateBindResult result) { 5141 p.headerless(resId == getBaseLayoutResource() 5142 || resId == getHeadsUpBaseLayoutResource() 5143 || resId == getMessagingLayoutResource() 5144 || resId == R.layout.notification_template_material_media); 5145 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 5146 5147 resetStandardTemplate(contentView); 5148 5149 final Bundle ex = mN.extras; 5150 updateBackgroundColor(contentView, p); 5151 bindNotificationHeader(contentView, p); 5152 bindLargeIconAndApplyMargin(contentView, p, result); 5153 boolean showProgress = handleProgressBar(contentView, ex, p); 5154 boolean hasSecondLine = showProgress; 5155 if (p.hasTitle()) { 5156 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE); 5157 contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title)); 5158 setTextViewColorPrimary(contentView, p.mTitleViewId, p); 5159 } else if (p.mTitleViewId != R.id.title) { 5160 // This alternate title view ID is not cleared by resetStandardTemplate 5161 contentView.setViewVisibility(p.mTitleViewId, View.GONE); 5162 contentView.setTextViewText(p.mTitleViewId, null); 5163 } 5164 if (p.text != null && p.text.length() != 0 5165 && (!showProgress || p.mAllowTextWithProgress)) { 5166 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE); 5167 contentView.setTextViewText(p.mTextViewId, processTextSpans(p.text)); 5168 setTextViewColorSecondary(contentView, p.mTextViewId, p); 5169 hasSecondLine = true; 5170 } else if (p.mTextViewId != R.id.text) { 5171 // This alternate text view ID is not cleared by resetStandardTemplate 5172 contentView.setViewVisibility(p.mTextViewId, View.GONE); 5173 contentView.setTextViewText(p.mTextViewId, null); 5174 } 5175 setHeaderlessVerticalMargins(contentView, p, hasSecondLine); 5176 5177 return contentView; 5178 } 5179 setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5180 private static void setHeaderlessVerticalMargins(RemoteViews contentView, 5181 StandardTemplateParams p, boolean hasSecondLine) { 5182 if (!p.mHeaderless) { 5183 return; 5184 } 5185 int marginDimen = hasSecondLine 5186 ? R.dimen.notification_headerless_margin_twoline 5187 : R.dimen.notification_headerless_margin_oneline; 5188 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5189 RemoteViews.MARGIN_TOP, marginDimen); 5190 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5191 RemoteViews.MARGIN_BOTTOM, marginDimen); 5192 } 5193 processTextSpans(CharSequence text)5194 private CharSequence processTextSpans(CharSequence text) { 5195 if (mInNightMode) { 5196 return ContrastColorUtil.clearColorSpans(text); 5197 } 5198 return text; 5199 } 5200 setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5201 private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, 5202 StandardTemplateParams p) { 5203 contentView.setTextColor(id, getPrimaryTextColor(p)); 5204 } 5205 5206 /** 5207 * @param p the template params to inflate this with 5208 * @return the primary text color 5209 * @hide 5210 */ 5211 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)5212 public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { 5213 return getColors(p).getPrimaryTextColor(); 5214 } 5215 5216 /** 5217 * @param p the template params to inflate this with 5218 * @return the secondary text color 5219 * @hide 5220 */ 5221 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)5222 public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { 5223 return getColors(p).getSecondaryTextColor(); 5224 } 5225 setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5226 private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, 5227 StandardTemplateParams p) { 5228 contentView.setTextColor(id, getSecondaryTextColor(p)); 5229 } 5230 getColors(StandardTemplateParams p)5231 private Colors getColors(StandardTemplateParams p) { 5232 mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode); 5233 return mColors; 5234 } 5235 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5236 private void updateBackgroundColor(RemoteViews contentView, 5237 StandardTemplateParams p) { 5238 if (isBackgroundColorized(p)) { 5239 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 5240 getBackgroundColor(p)); 5241 } else { 5242 // Clear it! 5243 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 5244 0); 5245 } 5246 } 5247 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5248 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 5249 StandardTemplateParams p) { 5250 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 5251 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 5252 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5253 if (!p.mHideProgress && (max != 0 || ind)) { 5254 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 5255 contentView.setProgressBar(R.id.progress, max, progress, ind); 5256 contentView.setProgressBackgroundTintList(R.id.progress, 5257 mContext.getColorStateList(R.color.notification_progress_background_color)); 5258 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p)); 5259 contentView.setProgressTintList(R.id.progress, progressTint); 5260 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint); 5261 return true; 5262 } else { 5263 contentView.setViewVisibility(R.id.progress, View.GONE); 5264 return false; 5265 } 5266 } 5267 bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5268 private void bindLargeIconAndApplyMargin(RemoteViews contentView, 5269 @NonNull StandardTemplateParams p, 5270 @Nullable TemplateBindResult result) { 5271 if (result == null) { 5272 result = new TemplateBindResult(); 5273 } 5274 bindLargeIcon(contentView, p, result); 5275 if (!p.mHeaderless) { 5276 // views in states with a header (big states) 5277 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header); 5278 result.mTitleMarginSet.applyToView(contentView, R.id.title); 5279 // If there is no title, the text (or big_text) needs to wrap around the image 5280 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId); 5281 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1); 5282 } 5283 } 5284 5285 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5286 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5287 // the change's state in NotificationManagerService were very complex. These behavior 5288 // changes are entirely visual, and should otherwise be undetectable by apps. 5289 @SuppressWarnings("AndroidFrameworkCompatChange") calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5290 private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, 5291 @NonNull TemplateBindResult result) { 5292 final Resources resources = mContext.getResources(); 5293 final float density = resources.getDisplayMetrics().density; 5294 final float iconMarginDp = resources.getDimension( 5295 R.dimen.notification_right_icon_content_margin) / density; 5296 final float contentMarginDp = resources.getDimension( 5297 R.dimen.notification_content_margin_end) / density; 5298 final float expanderSizeDp = resources.getDimension( 5299 R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; 5300 final float viewHeightDp = resources.getDimension( 5301 R.dimen.notification_right_icon_size) / density; 5302 float viewWidthDp = viewHeightDp; // icons are 1:1 by default 5303 if (rightIcon != null && (isPromotedPicture 5304 || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) { 5305 Drawable drawable = rightIcon.loadDrawable(mContext); 5306 if (drawable != null) { 5307 int iconWidth = drawable.getIntrinsicWidth(); 5308 int iconHeight = drawable.getIntrinsicHeight(); 5309 if (iconWidth > iconHeight && iconHeight > 0) { 5310 final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO; 5311 viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight, 5312 maxViewWidthDp); 5313 } 5314 } 5315 } 5316 final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; 5317 result.setRightIconState(rightIcon != null /* visible */, viewWidthDp, 5318 viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp); 5319 } 5320 5321 /** 5322 * Bind the large icon. 5323 */ bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5324 private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, 5325 @NonNull TemplateBindResult result) { 5326 if (mN.mLargeIcon == null && mN.largeIcon != null) { 5327 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 5328 } 5329 5330 // Determine the left and right icons 5331 Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon; 5332 Icon rightIcon = p.mHideRightIcon ? null 5333 : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon); 5334 5335 // Apply the left icon (without duplicating the bitmap) 5336 if (leftIcon != rightIcon || leftIcon == null) { 5337 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it 5338 // explicitly and make sure it won't take the right_icon drawable. 5339 contentView.setImageViewIcon(R.id.left_icon, leftIcon); 5340 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0); 5341 } else { 5342 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon 5343 // drawable. This avoids the view having two copies of the same bitmap. 5344 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1); 5345 } 5346 5347 // Always calculate dimens to populate `result` for the GONE case 5348 boolean isPromotedPicture = p.mPromotedPicture != null; 5349 calculateRightIconDimens(rightIcon, isPromotedPicture, result); 5350 5351 // Bind the right icon 5352 if (rightIcon != null) { 5353 contentView.setViewLayoutWidth(R.id.right_icon, 5354 result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP); 5355 contentView.setViewLayoutHeight(R.id.right_icon, 5356 result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP); 5357 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 5358 contentView.setImageViewIcon(R.id.right_icon, rightIcon); 5359 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 5360 isPromotedPicture ? 1 : 0); 5361 processLargeLegacyIcon(rightIcon, contentView, p); 5362 } else { 5363 // The "reset" doesn't clear the drawable, so we do it here. This clear is 5364 // important because the presence of a drawable in this view (regardless of the 5365 // visibility) is used by NotificationGroupingUtil to set the visibility. 5366 contentView.setImageViewIcon(R.id.right_icon, null); 5367 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0); 5368 } 5369 } 5370 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5371 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 5372 bindSmallIcon(contentView, p); 5373 // Populate text left-to-right so that separators are only shown between strings 5374 boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */); 5375 hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft); 5376 hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft); 5377 if (!hasTextToLeft) { 5378 // If there's still no text, force add the app name so there is some text. 5379 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */); 5380 } 5381 bindHeaderChronometerAndTime(contentView, p, hasTextToLeft); 5382 bindPhishingAlertIcon(contentView, p); 5383 bindProfileBadge(contentView, p); 5384 bindAlertedIcon(contentView, p); 5385 bindExpandButton(contentView, p); 5386 mN.mUsesStandardHeader = true; 5387 } 5388 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5389 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 5390 // set default colors 5391 int bgColor = getBackgroundColor(p); 5392 int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor); 5393 int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor); 5394 contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor); 5395 contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); 5396 // Use different highlighted colors for conversations' unread count 5397 if (p.mHighlightExpander) { 5398 pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor); 5399 textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor); 5400 } 5401 contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); 5402 contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); 5403 } 5404 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5405 private void bindHeaderChronometerAndTime(RemoteViews contentView, 5406 StandardTemplateParams p, boolean hasTextToLeft) { 5407 if (!p.mHideTime && showsTimeOrChronometer()) { 5408 if (hasTextToLeft) { 5409 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 5410 setTextViewColorSecondary(contentView, R.id.time_divider, p); 5411 } 5412 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 5413 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 5414 contentView.setLong(R.id.chronometer, "setBase", 5415 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 5416 contentView.setBoolean(R.id.chronometer, "setStarted", true); 5417 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 5418 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 5419 setTextViewColorSecondary(contentView, R.id.chronometer, p); 5420 } else { 5421 contentView.setViewVisibility(R.id.time, View.VISIBLE); 5422 contentView.setLong(R.id.time, "setTime", mN.when); 5423 setTextViewColorSecondary(contentView, R.id.time, p); 5424 } 5425 } else { 5426 // We still want a time to be set but gone, such that we can show and hide it 5427 // on demand in case it's a child notification without anything in the header 5428 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 5429 setTextViewColorSecondary(contentView, R.id.time, p); 5430 } 5431 } 5432 5433 /** 5434 * @return true if the header text will be visible 5435 */ bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5436 private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p, 5437 boolean hasTextToLeft) { 5438 if (p.mHideSubText) { 5439 return false; 5440 } 5441 CharSequence summaryText = p.summaryText; 5442 if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet 5443 && mStyle.hasSummaryInHeader()) { 5444 summaryText = mStyle.mSummaryText; 5445 } 5446 if (summaryText == null 5447 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 5448 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 5449 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 5450 } 5451 if (!TextUtils.isEmpty(summaryText)) { 5452 // TODO: Remove the span entirely to only have the string with propper formating. 5453 contentView.setTextViewText(R.id.header_text, processTextSpans( 5454 processLegacyText(summaryText))); 5455 setTextViewColorSecondary(contentView, R.id.header_text, p); 5456 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 5457 if (hasTextToLeft) { 5458 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 5459 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 5460 } 5461 return true; 5462 } 5463 return false; 5464 } 5465 5466 /** 5467 * @return true if the secondary header text will be visible 5468 */ bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5469 private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, 5470 boolean hasTextToLeft) { 5471 if (p.mHideSubText) { 5472 return false; 5473 } 5474 if (!TextUtils.isEmpty(p.headerTextSecondary)) { 5475 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( 5476 processLegacyText(p.headerTextSecondary))); 5477 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 5478 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 5479 if (hasTextToLeft) { 5480 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 5481 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 5482 } 5483 return true; 5484 } 5485 return false; 5486 } 5487 5488 /** 5489 * @hide 5490 */ 5491 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) loadHeaderAppName()5492 public String loadHeaderAppName() { 5493 CharSequence name = null; 5494 final PackageManager pm = mContext.getPackageManager(); 5495 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 5496 // only system packages which lump together a bunch of unrelated stuff 5497 // may substitute a different name to make the purpose of the 5498 // notification more clear. the correct package label should always 5499 // be accessible via SystemUI. 5500 final String pkg = mContext.getPackageName(); 5501 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 5502 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 5503 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 5504 name = subName; 5505 } else { 5506 Log.w(TAG, "warning: pkg " 5507 + pkg + " attempting to substitute app name '" + subName 5508 + "' without holding perm " 5509 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 5510 } 5511 } 5512 if (TextUtils.isEmpty(name)) { 5513 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 5514 } 5515 if (TextUtils.isEmpty(name)) { 5516 // still nothing? 5517 return null; 5518 } 5519 5520 return String.valueOf(name); 5521 } 5522 5523 /** 5524 * @return true if the app name will be visible 5525 */ bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5526 private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, 5527 boolean force) { 5528 if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) { 5529 // unless the force flag is set, don't show the app name in the minimized state. 5530 return false; 5531 } 5532 if (p.mHeaderless && p.hasTitle()) { 5533 // the headerless template will have the TITLE in this position; return true to 5534 // keep the divider visible between that title and the next text element. 5535 return true; 5536 } 5537 if (p.mHideAppName) { 5538 // The app name is being hidden, so we definitely want to return here. 5539 // Assume that there is a title which will replace it in the header. 5540 return p.hasTitle(); 5541 } 5542 contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); 5543 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 5544 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 5545 return true; 5546 } 5547 5548 /** 5549 * Determines if the notification should be colorized *for the purposes of applying colors*. 5550 * If this is the minimized view of a colorized notification, or if the app did not provide 5551 * a color to colorize with, this will return false so that internal coloring logic can 5552 * still render the notification normally. 5553 */ isBackgroundColorized(StandardTemplateParams p)5554 private boolean isBackgroundColorized(StandardTemplateParams p) { 5555 return p.allowColorization && mN.color != COLOR_DEFAULT && mN.isColorized(); 5556 } 5557 isCallActionColorCustomizable()5558 private boolean isCallActionColorCustomizable() { 5559 // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because 5560 // that is only used for disallowing colorization of headers for the minimized state, 5561 // and neither of those conditions applies when showing actions. 5562 // Not requiring StandardTemplateParams as an argument simplifies the creation process. 5563 return mN.color != COLOR_DEFAULT && mN.isColorized() 5564 && mContext.getResources().getBoolean( 5565 R.bool.config_callNotificationActionColorsRequireColorized); 5566 } 5567 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5568 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 5569 if (mN.mSmallIcon == null && mN.icon != 0) { 5570 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 5571 } 5572 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 5573 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 5574 processSmallIconColor(mN.mSmallIcon, contentView, p); 5575 } 5576 5577 /** 5578 * @return true if the built notification will show the time or the chronometer; false 5579 * otherwise 5580 */ showsTimeOrChronometer()5581 private boolean showsTimeOrChronometer() { 5582 return mN.showsTime() || mN.showsChronometer(); 5583 } 5584 resetStandardTemplateWithActions(RemoteViews big)5585 private void resetStandardTemplateWithActions(RemoteViews big) { 5586 // actions_container is only reset when there are no actions to avoid focus issues with 5587 // remote inputs. 5588 big.setViewVisibility(R.id.actions, View.GONE); 5589 big.removeAllViews(R.id.actions); 5590 5591 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 5592 big.setTextViewText(R.id.notification_material_reply_text_1, null); 5593 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 5594 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 5595 5596 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 5597 big.setTextViewText(R.id.notification_material_reply_text_2, null); 5598 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 5599 big.setTextViewText(R.id.notification_material_reply_text_3, null); 5600 5601 // This may get erased by bindSnoozeAction 5602 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5603 RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin); 5604 } 5605 bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5606 private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) { 5607 boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null 5608 || isBackgroundColorized(p) 5609 || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG; 5610 big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton); 5611 if (hideSnoozeButton) { 5612 // Only hide; NotificationContentView will show it when it adds the click listener 5613 big.setViewVisibility(R.id.snooze_button, View.GONE); 5614 } 5615 5616 final boolean snoozeEnabled = !hideSnoozeButton 5617 && mContext.getContentResolver() != null 5618 && isSnoozeSettingEnabled(); 5619 if (snoozeEnabled) { 5620 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5621 RemoteViews.MARGIN_BOTTOM, 0); 5622 } 5623 } 5624 isSnoozeSettingEnabled()5625 private boolean isSnoozeSettingEnabled() { 5626 try { 5627 return Settings.Secure.getInt(mContext.getContentResolver(), 5628 Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1; 5629 } catch (SecurityException ex) { 5630 // Most 3p apps can't access this snooze setting, so their NotificationListeners 5631 // would be unable to create notification views if we propagated this exception. 5632 return false; 5633 } 5634 } 5635 5636 /** 5637 * Returns the actions that are not contextual. 5638 */ getNonContextualActions()5639 private @NonNull List<Notification.Action> getNonContextualActions() { 5640 if (mActions == null) return Collections.emptyList(); 5641 List<Notification.Action> standardActions = new ArrayList<>(); 5642 for (Notification.Action action : mActions) { 5643 if (!action.isContextual()) { 5644 standardActions.add(action); 5645 } 5646 } 5647 return standardActions; 5648 } 5649 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5650 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5651 StandardTemplateParams p, TemplateBindResult result) { 5652 RemoteViews big = applyStandardTemplate(layoutId, p, result); 5653 5654 resetStandardTemplateWithActions(big); 5655 bindSnoozeAction(big, p); 5656 // color the snooze and bubble actions with the theme color 5657 ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); 5658 big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); 5659 big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); 5660 5661 boolean validRemoteInput = false; 5662 5663 // In the UI, contextual actions appear separately from the standard actions, so we 5664 // filter them out here. 5665 List<Notification.Action> nonContextualActions = getNonContextualActions(); 5666 5667 int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS); 5668 boolean emphazisedMode = mN.fullScreenIntent != null || p.mCallStyleActions; 5669 if (p.mCallStyleActions) { 5670 // Clear view padding to allow buttons to start on the left edge. 5671 // This must be done before 'setEmphasizedMode' which sets top/bottom margins. 5672 big.setViewPadding(R.id.actions, 0, 0, 0, 0); 5673 // Add an optional indent that will make buttons start at the correct column when 5674 // there is enough space to do so (and fall back to the left edge if not). 5675 big.setInt(R.id.actions, "setCollapsibleIndentDimen", 5676 R.dimen.call_notification_collapsible_indent); 5677 } 5678 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); 5679 if (numActions > 0 && !p.mHideActions) { 5680 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 5681 big.setViewVisibility(R.id.actions, View.VISIBLE); 5682 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5683 RemoteViews.MARGIN_BOTTOM, 0); 5684 for (int i = 0; i < numActions; i++) { 5685 Action action = nonContextualActions.get(i); 5686 5687 boolean actionHasValidInput = hasValidRemoteInput(action); 5688 validRemoteInput |= actionHasValidInput; 5689 5690 final RemoteViews button = generateActionButton(action, emphazisedMode, p); 5691 if (actionHasValidInput && !emphazisedMode) { 5692 // Clear the drawable 5693 button.setInt(R.id.action0, "setBackgroundResource", 0); 5694 } 5695 if (emphazisedMode && i > 0) { 5696 // Clear start margin from non-first buttons to reduce the gap between them. 5697 // (8dp remaining gap is from all buttons' standard 4dp inset). 5698 button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0); 5699 } 5700 big.addView(R.id.actions, button); 5701 } 5702 } else { 5703 big.setViewVisibility(R.id.actions_container, View.GONE); 5704 } 5705 5706 RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( 5707 mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); 5708 if (validRemoteInput && replyText != null && replyText.length > 0 5709 && !TextUtils.isEmpty(replyText[0].getText()) 5710 && p.maxRemoteInputHistory > 0) { 5711 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 5712 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 5713 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 5714 View.VISIBLE); 5715 big.setTextViewText(R.id.notification_material_reply_text_1, 5716 processTextSpans(replyText[0].getText())); 5717 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 5718 big.setViewVisibility(R.id.notification_material_reply_progress, 5719 showSpinner ? View.VISIBLE : View.GONE); 5720 big.setProgressIndeterminateTintList( 5721 R.id.notification_material_reply_progress, 5722 ColorStateList.valueOf(getPrimaryAccentColor(p))); 5723 5724 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) 5725 && p.maxRemoteInputHistory > 1) { 5726 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 5727 big.setTextViewText(R.id.notification_material_reply_text_2, 5728 processTextSpans(replyText[1].getText())); 5729 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 5730 5731 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) 5732 && p.maxRemoteInputHistory > 2) { 5733 big.setViewVisibility( 5734 R.id.notification_material_reply_text_3, View.VISIBLE); 5735 big.setTextViewText(R.id.notification_material_reply_text_3, 5736 processTextSpans(replyText[2].getText())); 5737 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 5738 } 5739 } 5740 } 5741 5742 return big; 5743 } 5744 hasValidRemoteInput(Action action)5745 private boolean hasValidRemoteInput(Action action) { 5746 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 5747 // Weird actions 5748 return false; 5749 } 5750 5751 RemoteInput[] remoteInputs = action.getRemoteInputs(); 5752 if (remoteInputs == null) { 5753 return false; 5754 } 5755 5756 for (RemoteInput r : remoteInputs) { 5757 CharSequence[] choices = r.getChoices(); 5758 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 5759 return true; 5760 } 5761 } 5762 return false; 5763 } 5764 5765 /** 5766 * Construct a RemoteViews for the final 1U notification layout. In order: 5767 * 1. Custom contentView from the caller 5768 * 2. Style's proposed content view 5769 * 3. Standard template view 5770 */ createContentView()5771 public RemoteViews createContentView() { 5772 return createContentView(false /* increasedheight */ ); 5773 } 5774 5775 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5776 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5777 // the change's state in NotificationManagerService were very complex. While it's possible 5778 // apps can detect the change, it's most likely that the changes will simply result in 5779 // visual regressions. 5780 @SuppressWarnings("AndroidFrameworkCompatChange") fullyCustomViewRequiresDecoration(boolean fromStyle)5781 private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) { 5782 // Custom views which come from a platform style class are safe, and thus do not need to 5783 // be wrapped. Any subclass of those styles has the opportunity to make arbitrary 5784 // changes to the RemoteViews, and thus can't be trusted as a fully vetted view. 5785 if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { 5786 return false; 5787 } 5788 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S; 5789 } 5790 minimallyDecoratedContentView(@onNull RemoteViews customContent)5791 private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) { 5792 StandardTemplateParams p = mParams.reset() 5793 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 5794 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5795 .fillTextsFrom(this); 5796 TemplateBindResult result = new TemplateBindResult(); 5797 RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result); 5798 buildCustomContentIntoTemplate(mContext, standard, customContent, 5799 p, result); 5800 return standard; 5801 } 5802 minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5803 private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) { 5804 StandardTemplateParams p = mParams.reset() 5805 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 5806 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5807 .fillTextsFrom(this); 5808 TemplateBindResult result = new TemplateBindResult(); 5809 RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5810 p, result); 5811 buildCustomContentIntoTemplate(mContext, standard, customContent, 5812 p, result); 5813 makeHeaderExpanded(standard); 5814 return standard; 5815 } 5816 minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5817 private RemoteViews minimallyDecoratedHeadsUpContentView( 5818 @NonNull RemoteViews customContent) { 5819 StandardTemplateParams p = mParams.reset() 5820 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 5821 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5822 .fillTextsFrom(this); 5823 TemplateBindResult result = new TemplateBindResult(); 5824 RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), 5825 p, result); 5826 buildCustomContentIntoTemplate(mContext, standard, customContent, 5827 p, result); 5828 return standard; 5829 } 5830 5831 /** 5832 * Construct a RemoteViews for the smaller content view. 5833 * 5834 * @param increasedHeight true if this layout be created with an increased height. Some 5835 * styles may support showing more then just that basic 1U size 5836 * and the system may decide to render important notifications 5837 * slightly bigger even when collapsed. 5838 * 5839 * @hide 5840 */ createContentView(boolean increasedHeight)5841 public RemoteViews createContentView(boolean increasedHeight) { 5842 if (useExistingRemoteView(mN.contentView)) { 5843 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5844 ? minimallyDecoratedContentView(mN.contentView) : mN.contentView; 5845 } else if (mStyle != null) { 5846 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 5847 if (styleView != null) { 5848 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 5849 ? minimallyDecoratedContentView(styleView) : styleView; 5850 } 5851 } 5852 StandardTemplateParams p = mParams.reset() 5853 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 5854 .fillTextsFrom(this); 5855 return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */); 5856 } 5857 useExistingRemoteView(RemoteViews customContent)5858 private boolean useExistingRemoteView(RemoteViews customContent) { 5859 if (customContent == null) { 5860 return false; 5861 } 5862 if (styleDisplaysCustomViewInline()) { 5863 // the provided custom view is intended to be wrapped by the style. 5864 return false; 5865 } 5866 if (fullyCustomViewRequiresDecoration(false) 5867 && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) { 5868 // If the app's custom views are objects returned from Builder.create*ContentView() 5869 // then the app is most likely attempting to spoof the user. Even if they are not, 5870 // the result would be broken (b/189189308) so we will ignore it. 5871 Log.w(TAG, "For apps targeting S, a custom content view that is a modified " 5872 + "version of any standard layout is disallowed."); 5873 return false; 5874 } 5875 return true; 5876 } 5877 5878 /** 5879 * Construct a RemoteViews for the final big notification layout. 5880 */ createBigContentView()5881 public RemoteViews createBigContentView() { 5882 RemoteViews result = null; 5883 if (useExistingRemoteView(mN.bigContentView)) { 5884 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5885 ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView; 5886 } 5887 if (mStyle != null) { 5888 result = mStyle.makeBigContentView(); 5889 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) { 5890 result = minimallyDecoratedBigContentView(result); 5891 } 5892 } 5893 if (result == null) { 5894 if (bigContentViewRequired()) { 5895 StandardTemplateParams p = mParams.reset() 5896 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 5897 .allowTextWithProgress(true) 5898 .fillTextsFrom(this); 5899 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, 5900 null /* result */); 5901 } 5902 } 5903 makeHeaderExpanded(result); 5904 return result; 5905 } 5906 5907 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5908 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5909 // the change's state in NotificationManagerService were very complex. While it's possible 5910 // apps can detect the change, it's most likely that the changes will simply result in 5911 // visual regressions. 5912 @SuppressWarnings("AndroidFrameworkCompatChange") bigContentViewRequired()5913 private boolean bigContentViewRequired() { 5914 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { 5915 return true; 5916 } 5917 // Notifications with contentView and without a bigContentView, style, or actions would 5918 // not have an expanded state before S, so showing the standard template expanded state 5919 // usually looks wrong, so we keep it simple and don't show the expanded state. 5920 boolean exempt = mN.contentView != null && mN.bigContentView == null 5921 && mStyle == null && mActions.size() == 0; 5922 return !exempt; 5923 } 5924 5925 /** 5926 * Construct a RemoteViews for the final notification header only. This will not be 5927 * colorized. 5928 * 5929 * @hide 5930 */ makeNotificationGroupHeader()5931 public RemoteViews makeNotificationGroupHeader() { 5932 return makeNotificationHeader(mParams.reset() 5933 .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER) 5934 .fillTextsFrom(this)); 5935 } 5936 5937 /** 5938 * Construct a RemoteViews for the final notification header only. This will not be 5939 * colorized. 5940 * 5941 * @param p the template params to inflate this with 5942 */ makeNotificationHeader(StandardTemplateParams p)5943 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 5944 // Headers on their own are never colorized 5945 p.disallowColorization(); 5946 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 5947 R.layout.notification_template_header); 5948 resetNotificationHeader(header); 5949 bindNotificationHeader(header, p); 5950 return header; 5951 } 5952 5953 /** 5954 * Construct a RemoteViews for the ambient version of the notification. 5955 * 5956 * @hide 5957 */ makeAmbientNotification()5958 public RemoteViews makeAmbientNotification() { 5959 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 5960 if (headsUpContentView != null) { 5961 return headsUpContentView; 5962 } 5963 return createContentView(); 5964 } 5965 5966 /** 5967 * Adapt the Notification header if this view is used as an expanded view. 5968 * 5969 * @hide 5970 */ makeHeaderExpanded(RemoteViews result)5971 public static void makeHeaderExpanded(RemoteViews result) { 5972 if (result != null) { 5973 result.setBoolean(R.id.expand_button, "setExpanded", true); 5974 } 5975 } 5976 5977 /** 5978 * Construct a RemoteViews for the final heads-up notification layout. 5979 * 5980 * @param increasedHeight true if this layout be created with an increased height. Some 5981 * styles may support showing more then just that basic 1U size 5982 * and the system may decide to render important notifications 5983 * slightly bigger even when collapsed. 5984 * 5985 * @hide 5986 */ createHeadsUpContentView(boolean increasedHeight)5987 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 5988 if (useExistingRemoteView(mN.headsUpContentView)) { 5989 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5990 ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) 5991 : mN.headsUpContentView; 5992 } else if (mStyle != null) { 5993 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 5994 if (styleView != null) { 5995 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 5996 ? minimallyDecoratedHeadsUpContentView(styleView) : styleView; 5997 } 5998 } else if (mActions.size() == 0) { 5999 return null; 6000 } 6001 6002 // We only want at most a single remote input history to be shown here, otherwise 6003 // the content would become squished. 6004 StandardTemplateParams p = mParams.reset() 6005 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 6006 .fillTextsFrom(this) 6007 .setMaxRemoteInputHistory(1); 6008 return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, 6009 null /* result */); 6010 } 6011 6012 /** 6013 * Construct a RemoteViews for the final heads-up notification layout. 6014 */ createHeadsUpContentView()6015 public RemoteViews createHeadsUpContentView() { 6016 return createHeadsUpContentView(false /* useIncreasedHeight */); 6017 } 6018 6019 /** 6020 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 6021 * 6022 * @param isLowPriority is this notification low priority 6023 * @hide 6024 */ 6025 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)6026 public RemoteViews makePublicContentView(boolean isLowPriority) { 6027 if (mN.publicVersion != null) { 6028 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 6029 return builder.createContentView(); 6030 } 6031 Bundle savedBundle = mN.extras; 6032 Style style = mStyle; 6033 mStyle = null; 6034 Icon largeIcon = mN.mLargeIcon; 6035 mN.mLargeIcon = null; 6036 Bitmap largeIconLegacy = mN.largeIcon; 6037 mN.largeIcon = null; 6038 ArrayList<Action> actions = mActions; 6039 mActions = new ArrayList<>(); 6040 Bundle publicExtras = new Bundle(); 6041 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 6042 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 6043 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 6044 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 6045 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 6046 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 6047 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 6048 if (appName != null) { 6049 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 6050 } 6051 mN.extras = publicExtras; 6052 RemoteViews view; 6053 StandardTemplateParams params = mParams.reset() 6054 .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC) 6055 .fillTextsFrom(this); 6056 if (isLowPriority) { 6057 params.highlightExpander(false); 6058 } 6059 view = makeNotificationHeader(params); 6060 view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true); 6061 mN.extras = savedBundle; 6062 mN.mLargeIcon = largeIcon; 6063 mN.largeIcon = largeIconLegacy; 6064 mActions = actions; 6065 mStyle = style; 6066 return view; 6067 } 6068 6069 /** 6070 * Construct a content view for the display when low - priority 6071 * 6072 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise 6073 * a new subtext is created consisting of the content of the 6074 * notification. 6075 * @hide 6076 */ makeLowPriorityContentView(boolean useRegularSubtext)6077 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { 6078 StandardTemplateParams p = mParams.reset() 6079 .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED) 6080 .highlightExpander(false) 6081 .fillTextsFrom(this); 6082 if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) { 6083 p.summaryText(createSummaryText()); 6084 } 6085 RemoteViews header = makeNotificationHeader(p); 6086 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true); 6087 // The low priority header has no app name and shows the text 6088 header.setBoolean(R.id.notification_header, "styleTextAsTitle", true); 6089 return header; 6090 } 6091 createSummaryText()6092 private CharSequence createSummaryText() { 6093 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE); 6094 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) { 6095 return titleText; 6096 } 6097 SpannableStringBuilder summary = new SpannableStringBuilder(); 6098 if (titleText == null) { 6099 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG); 6100 } 6101 BidiFormatter bidi = BidiFormatter.getInstance(); 6102 if (titleText != null) { 6103 summary.append(bidi.unicodeWrap(titleText)); 6104 } 6105 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT); 6106 if (titleText != null && contentText != null) { 6107 summary.append(bidi.unicodeWrap(mContext.getText( 6108 R.string.notification_header_divider_symbol_with_spaces))); 6109 } 6110 if (contentText != null) { 6111 summary.append(bidi.unicodeWrap(contentText)); 6112 } 6113 return summary; 6114 } 6115 generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6116 private RemoteViews generateActionButton(Action action, boolean emphasizedMode, 6117 StandardTemplateParams p) { 6118 final boolean tombstone = (action.actionIntent == null); 6119 RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 6120 emphasizedMode ? getEmphasizedActionLayoutResource() 6121 : tombstone ? getActionTombstoneLayoutResource() 6122 : getActionLayoutResource()); 6123 if (!tombstone) { 6124 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 6125 } 6126 button.setContentDescription(R.id.action0, action.title); 6127 if (action.mRemoteInputs != null) { 6128 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 6129 } 6130 if (emphasizedMode) { 6131 // change the background bgColor 6132 CharSequence title = action.title; 6133 int buttonFillColor = getColors(p).getSecondaryAccentColor(); 6134 if (isLegacy()) { 6135 title = ContrastColorUtil.clearColorSpans(title); 6136 } else { 6137 // Check for a full-length span color to use as the button fill color. 6138 Integer fullLengthColor = getFullLengthSpanColor(title); 6139 if (fullLengthColor != null) { 6140 // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. 6141 int notifBackgroundColor = getColors(p).getBackgroundColor(); 6142 buttonFillColor = ensureButtonFillContrast( 6143 fullLengthColor, notifBackgroundColor); 6144 } 6145 // Remove full-length color spans and ensure text contrast with the button fill. 6146 title = ensureColorSpanContrast(title, buttonFillColor); 6147 } 6148 button.setTextViewText(R.id.action0, processTextSpans(title)); 6149 final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, 6150 buttonFillColor, mInNightMode); 6151 button.setTextColor(R.id.action0, textColor); 6152 // We only want about 20% alpha for the ripple 6153 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000; 6154 button.setColorStateList(R.id.action0, "setRippleColor", 6155 ColorStateList.valueOf(rippleColor)); 6156 button.setColorStateList(R.id.action0, "setButtonBackground", 6157 ColorStateList.valueOf(buttonFillColor)); 6158 if (p.mCallStyleActions) { 6159 button.setImageViewIcon(R.id.action0, action.getIcon()); 6160 boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY); 6161 button.setBoolean(R.id.action0, "setIsPriority", priority); 6162 int minWidthDimen = 6163 priority ? R.dimen.call_notification_system_action_min_width : 0; 6164 button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen); 6165 } 6166 } else { 6167 button.setTextViewText(R.id.action0, processTextSpans( 6168 processLegacyText(action.title))); 6169 button.setTextColor(R.id.action0, getStandardActionColor(p)); 6170 } 6171 // CallStyle notifications add action buttons which don't actually exist in mActions, 6172 // so we have to omit the index in that case. 6173 int actionIndex = mActions.indexOf(action); 6174 if (actionIndex != -1) { 6175 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex); 6176 } 6177 return button; 6178 } 6179 6180 /** 6181 * Extract the color from a full-length span from the text. 6182 * 6183 * @param charSequence the charSequence containing spans 6184 * @return the raw color of the text's last full-length span containing a color, or null if 6185 * no full-length span sets the text color. 6186 * @hide 6187 */ 6188 @VisibleForTesting 6189 @Nullable getFullLengthSpanColor(CharSequence charSequence)6190 public static Integer getFullLengthSpanColor(CharSequence charSequence) { 6191 // NOTE: this method preserves the functionality that for a CharSequence with multiple 6192 // full-length spans, the color of the last one is used. 6193 Integer result = null; 6194 if (charSequence instanceof Spanned) { 6195 Spanned ss = (Spanned) charSequence; 6196 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 6197 // First read through all full-length spans to get the button fill color, which will 6198 // be used as the background color for ensuring contrast of non-full-length spans. 6199 for (Object span : spans) { 6200 int spanStart = ss.getSpanStart(span); 6201 int spanEnd = ss.getSpanEnd(span); 6202 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 6203 if (!fullLength) { 6204 continue; 6205 } 6206 if (span instanceof TextAppearanceSpan) { 6207 TextAppearanceSpan originalSpan = (TextAppearanceSpan) span; 6208 ColorStateList textColor = originalSpan.getTextColor(); 6209 if (textColor != null) { 6210 result = textColor.getDefaultColor(); 6211 } 6212 } else if (span instanceof ForegroundColorSpan) { 6213 ForegroundColorSpan originalSpan = (ForegroundColorSpan) span; 6214 result = originalSpan.getForegroundColor(); 6215 } 6216 } 6217 } 6218 return result; 6219 } 6220 6221 /** 6222 * Ensures contrast on color spans against a background color. 6223 * Note that any full-length color spans will be removed instead of being contrasted. 6224 * 6225 * @param charSequence the charSequence on which the spans are 6226 * @param background the background color to ensure the contrast against 6227 * @return the contrasted charSequence 6228 * @hide 6229 */ 6230 @VisibleForTesting ensureColorSpanContrast(CharSequence charSequence, int background)6231 public static CharSequence ensureColorSpanContrast(CharSequence charSequence, 6232 int background) { 6233 if (charSequence instanceof Spanned) { 6234 Spanned ss = (Spanned) charSequence; 6235 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 6236 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 6237 for (Object span : spans) { 6238 Object resultSpan = span; 6239 int spanStart = ss.getSpanStart(span); 6240 int spanEnd = ss.getSpanEnd(span); 6241 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 6242 if (resultSpan instanceof CharacterStyle) { 6243 resultSpan = ((CharacterStyle) span).getUnderlying(); 6244 } 6245 if (resultSpan instanceof TextAppearanceSpan) { 6246 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 6247 ColorStateList textColor = originalSpan.getTextColor(); 6248 if (textColor != null) { 6249 if (fullLength) { 6250 // Let's drop the color from the span 6251 textColor = null; 6252 } else { 6253 int[] colors = textColor.getColors(); 6254 int[] newColors = new int[colors.length]; 6255 for (int i = 0; i < newColors.length; i++) { 6256 boolean isBgDark = isColorDark(background); 6257 newColors[i] = ContrastColorUtil.ensureLargeTextContrast( 6258 colors[i], background, isBgDark); 6259 } 6260 textColor = new ColorStateList(textColor.getStates().clone(), 6261 newColors); 6262 } 6263 resultSpan = new TextAppearanceSpan( 6264 originalSpan.getFamily(), 6265 originalSpan.getTextStyle(), 6266 originalSpan.getTextSize(), 6267 textColor, 6268 originalSpan.getLinkTextColor()); 6269 } 6270 } else if (resultSpan instanceof ForegroundColorSpan) { 6271 if (fullLength) { 6272 resultSpan = null; 6273 } else { 6274 ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; 6275 int foregroundColor = originalSpan.getForegroundColor(); 6276 boolean isBgDark = isColorDark(background); 6277 foregroundColor = ContrastColorUtil.ensureLargeTextContrast( 6278 foregroundColor, background, isBgDark); 6279 resultSpan = new ForegroundColorSpan(foregroundColor); 6280 } 6281 } else { 6282 resultSpan = span; 6283 } 6284 if (resultSpan != null) { 6285 builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span)); 6286 } 6287 } 6288 return builder; 6289 } 6290 return charSequence; 6291 } 6292 6293 /** 6294 * Determines if the color is light or dark. Specifically, this is using the same metric as 6295 * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that 6296 * the direction of color shift is consistent. 6297 * 6298 * @param color the color to check 6299 * @return true if the color has higher contrast with white than black 6300 * @hide 6301 */ isColorDark(int color)6302 public static boolean isColorDark(int color) { 6303 // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint. 6304 return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474; 6305 } 6306 6307 /** 6308 * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue 6309 * as the original color, but is lightened or darkened depending on whether the background 6310 * is dark or light. 6311 * 6312 * @hide 6313 */ 6314 @VisibleForTesting ensureButtonFillContrast(int color, int bg)6315 public static int ensureButtonFillContrast(int color, int bg) { 6316 return isColorDark(bg) 6317 ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3) 6318 : ContrastColorUtil.findContrastColor(color, bg, true, 1.3); 6319 } 6320 6321 6322 /** 6323 * @return Whether we are currently building a notification from a legacy (an app that 6324 * doesn't create material notifications by itself) app. 6325 */ isLegacy()6326 private boolean isLegacy() { 6327 if (!mIsLegacyInitialized) { 6328 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion 6329 < Build.VERSION_CODES.LOLLIPOP; 6330 mIsLegacyInitialized = true; 6331 } 6332 return mIsLegacy; 6333 } 6334 6335 private CharSequence processLegacyText(CharSequence charSequence) { 6336 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion(); 6337 if (isAlreadyLightText) { 6338 return getColorUtil().invertCharSequenceColors(charSequence); 6339 } else { 6340 return charSequence; 6341 } 6342 } 6343 6344 /** 6345 * Apply any necessary colors to the small icon 6346 */ 6347 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, 6348 StandardTemplateParams p) { 6349 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); 6350 int color = getSmallIconColor(p); 6351 contentView.setInt(R.id.icon, "setBackgroundColor", 6352 getBackgroundColor(p)); 6353 contentView.setInt(R.id.icon, "setOriginalIconColor", 6354 colorable ? color : COLOR_INVALID); 6355 } 6356 6357 /** 6358 * Make the largeIcon dark if it's a fake smallIcon (that is, 6359 * if it's grayscale). 6360 */ 6361 // TODO: also check bounds, transparency, that sort of thing. 6362 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView, 6363 StandardTemplateParams p) { 6364 if (largeIcon != null && isLegacy() 6365 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 6366 // resolve color will fall back to the default when legacy 6367 int color = getSmallIconColor(p); 6368 contentView.setInt(R.id.icon, "setOriginalIconColor", color); 6369 } 6370 } 6371 6372 private void sanitizeColor() { 6373 if (mN.color != COLOR_DEFAULT) { 6374 mN.color |= 0xFF000000; // no alpha for custom colors 6375 } 6376 } 6377 6378 /** 6379 * Gets the standard action button color 6380 */ 6381 private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { 6382 return mTintActionButtons || isBackgroundColorized(p) 6383 ? getPrimaryAccentColor(p) : getSecondaryTextColor(p); 6384 } 6385 6386 /** 6387 * Gets the foreground color of the small icon. If the notification is colorized, this 6388 * is the primary text color, otherwise it's the contrast-adjusted app-provided color. 6389 */ 6390 private @ColorInt int getSmallIconColor(StandardTemplateParams p) { 6391 return getColors(p).getContrastColor(); 6392 } 6393 6394 /** @return the theme's accent color for colored UI elements. */ 6395 private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) { 6396 return getColors(p).getPrimaryAccentColor(); 6397 } 6398 6399 /** 6400 * Apply the unstyled operations and return a new {@link Notification} object. 6401 * @hide 6402 */ 6403 @NonNull 6404 public Notification buildUnstyled() { 6405 if (mActions.size() > 0) { 6406 mN.actions = new Action[mActions.size()]; 6407 mActions.toArray(mN.actions); 6408 } 6409 if (!mPersonList.isEmpty()) { 6410 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); 6411 } 6412 if (mN.bigContentView != null || mN.contentView != null 6413 || mN.headsUpContentView != null) { 6414 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 6415 } 6416 return mN; 6417 } 6418 6419 /** 6420 * Creates a Builder from an existing notification so further changes can be made. 6421 * @param context The context for your application / activity. 6422 * @param n The notification to create a Builder from. 6423 */ 6424 @NonNull recoverBuilder(Context context, Notification n)6425 public static Notification.Builder recoverBuilder(Context context, Notification n) { 6426 // Re-create notification context so we can access app resources. 6427 ApplicationInfo applicationInfo = n.extras.getParcelable( 6428 EXTRA_BUILDER_APPLICATION_INFO); 6429 Context builderContext; 6430 if (applicationInfo != null) { 6431 try { 6432 builderContext = context.createApplicationContext(applicationInfo, 6433 Context.CONTEXT_RESTRICTED); 6434 } catch (NameNotFoundException e) { 6435 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 6436 builderContext = context; // try with our context 6437 } 6438 } else { 6439 builderContext = context; // try with given context 6440 } 6441 6442 return new Builder(builderContext, n); 6443 } 6444 6445 /** 6446 * Determines whether the platform can generate contextual actions for a notification. 6447 * By default this is true. 6448 */ 6449 @NonNull setAllowSystemGeneratedContextualActions(boolean allowed)6450 public Builder setAllowSystemGeneratedContextualActions(boolean allowed) { 6451 mN.mAllowSystemGeneratedContextualActions = allowed; 6452 return this; 6453 } 6454 6455 /** 6456 * @deprecated Use {@link #build()} instead. 6457 */ 6458 @Deprecated getNotification()6459 public Notification getNotification() { 6460 return build(); 6461 } 6462 6463 /** 6464 * Combine all of the options that have been set and return a new {@link Notification} 6465 * object. 6466 * 6467 * If this notification has {@link BubbleMetadata} attached that was created with 6468 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 6469 * metadata matches the shortcutId set on the notification builder, if one was set. 6470 * If the shortcutId's were specified but do not match, an exception is thrown here. 6471 * 6472 * @see BubbleMetadata.Builder#Builder(String) 6473 * @see #setShortcutId(String) 6474 */ 6475 @NonNull build()6476 public Notification build() { 6477 // Check shortcut id matches 6478 if (mN.mShortcutId != null 6479 && mN.mBubbleMetadata != null 6480 && mN.mBubbleMetadata.getShortcutId() != null 6481 && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { 6482 throw new IllegalArgumentException( 6483 "Notification and BubbleMetadata shortcut id's don't match," 6484 + " notification: " + mN.mShortcutId 6485 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); 6486 } 6487 6488 // first, add any extras from the calling code 6489 if (mUserExtras != null) { 6490 mN.extras = getAllExtras(); 6491 } 6492 6493 mN.creationTime = System.currentTimeMillis(); 6494 6495 // lazy stuff from mContext; see comment in Builder(Context, Notification) 6496 Notification.addFieldsFromContext(mContext, mN); 6497 6498 buildUnstyled(); 6499 6500 if (mStyle != null) { 6501 mStyle.reduceImageSizes(mContext); 6502 mStyle.purgeResources(); 6503 mStyle.validate(mContext); 6504 mStyle.buildStyled(mN); 6505 } 6506 mN.reduceImageSizes(mContext); 6507 6508 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 6509 && !styleDisplaysCustomViewInline()) { 6510 RemoteViews newContentView = mN.contentView; 6511 RemoteViews newBigContentView = mN.bigContentView; 6512 RemoteViews newHeadsUpContentView = mN.headsUpContentView; 6513 if (newContentView == null) { 6514 newContentView = createContentView(); 6515 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 6516 newContentView.getSequenceNumber()); 6517 } 6518 if (newBigContentView == null) { 6519 newBigContentView = createBigContentView(); 6520 if (newBigContentView != null) { 6521 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 6522 newBigContentView.getSequenceNumber()); 6523 } 6524 } 6525 if (newHeadsUpContentView == null) { 6526 newHeadsUpContentView = createHeadsUpContentView(); 6527 if (newHeadsUpContentView != null) { 6528 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 6529 newHeadsUpContentView.getSequenceNumber()); 6530 } 6531 } 6532 // Don't set any of the content views until after they have all been generated, 6533 // to avoid the generated .contentView triggering the logic which skips generating 6534 // the .bigContentView. 6535 mN.contentView = newContentView; 6536 mN.bigContentView = newBigContentView; 6537 mN.headsUpContentView = newHeadsUpContentView; 6538 } 6539 6540 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 6541 mN.flags |= FLAG_SHOW_LIGHTS; 6542 } 6543 6544 mN.allPendingIntents = null; 6545 6546 return mN; 6547 } 6548 styleDisplaysCustomViewInline()6549 private boolean styleDisplaysCustomViewInline() { 6550 return mStyle != null && mStyle.displayCustomViewInline(); 6551 } 6552 6553 /** 6554 * Apply this Builder to an existing {@link Notification} object. 6555 * 6556 * @hide 6557 */ 6558 @NonNull buildInto(@onNull Notification n)6559 public Notification buildInto(@NonNull Notification n) { 6560 build().cloneInto(n, true); 6561 return n; 6562 } 6563 6564 /** 6565 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 6566 * change. 6567 * 6568 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 6569 * 6570 * @hide 6571 */ maybeCloneStrippedForDelivery(Notification n)6572 public static Notification maybeCloneStrippedForDelivery(Notification n) { 6573 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 6574 6575 // Only strip views for known Styles because we won't know how to 6576 // re-create them otherwise. 6577 if (!TextUtils.isEmpty(templateClass) 6578 && getNotificationStyleClass(templateClass) == null) { 6579 return n; 6580 } 6581 6582 // Only strip unmodified BuilderRemoteViews. 6583 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 6584 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 6585 n.contentView.getSequenceNumber(); 6586 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 6587 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 6588 n.bigContentView.getSequenceNumber(); 6589 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 6590 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 6591 n.headsUpContentView.getSequenceNumber(); 6592 6593 // Nothing to do here, no need to clone. 6594 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 6595 return n; 6596 } 6597 6598 Notification clone = n.clone(); 6599 if (stripContentView) { 6600 clone.contentView = null; 6601 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 6602 } 6603 if (stripBigContentView) { 6604 clone.bigContentView = null; 6605 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 6606 } 6607 if (stripHeadsUpContentView) { 6608 clone.headsUpContentView = null; 6609 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 6610 } 6611 return clone; 6612 } 6613 6614 @UnsupportedAppUsage getBaseLayoutResource()6615 private int getBaseLayoutResource() { 6616 return R.layout.notification_template_material_base; 6617 } 6618 getHeadsUpBaseLayoutResource()6619 private int getHeadsUpBaseLayoutResource() { 6620 return R.layout.notification_template_material_heads_up_base; 6621 } 6622 getBigBaseLayoutResource()6623 private int getBigBaseLayoutResource() { 6624 return R.layout.notification_template_material_big_base; 6625 } 6626 getBigPictureLayoutResource()6627 private int getBigPictureLayoutResource() { 6628 return R.layout.notification_template_material_big_picture; 6629 } 6630 getBigTextLayoutResource()6631 private int getBigTextLayoutResource() { 6632 return R.layout.notification_template_material_big_text; 6633 } 6634 getInboxLayoutResource()6635 private int getInboxLayoutResource() { 6636 return R.layout.notification_template_material_inbox; 6637 } 6638 getMessagingLayoutResource()6639 private int getMessagingLayoutResource() { 6640 return R.layout.notification_template_material_messaging; 6641 } 6642 getBigMessagingLayoutResource()6643 private int getBigMessagingLayoutResource() { 6644 return R.layout.notification_template_material_big_messaging; 6645 } 6646 getConversationLayoutResource()6647 private int getConversationLayoutResource() { 6648 return R.layout.notification_template_material_conversation; 6649 } 6650 getActionLayoutResource()6651 private int getActionLayoutResource() { 6652 return R.layout.notification_material_action; 6653 } 6654 getEmphasizedActionLayoutResource()6655 private int getEmphasizedActionLayoutResource() { 6656 return R.layout.notification_material_action_emphasized; 6657 } 6658 getActionTombstoneLayoutResource()6659 private int getActionTombstoneLayoutResource() { 6660 return R.layout.notification_material_action_tombstone; 6661 } 6662 getBackgroundColor(StandardTemplateParams p)6663 private @ColorInt int getBackgroundColor(StandardTemplateParams p) { 6664 return getColors(p).getBackgroundColor(); 6665 } 6666 textColorsNeedInversion()6667 private boolean textColorsNeedInversion() { 6668 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { 6669 return false; 6670 } 6671 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 6672 return targetSdkVersion > Build.VERSION_CODES.M 6673 && targetSdkVersion < Build.VERSION_CODES.O; 6674 } 6675 6676 /** 6677 * Get the text that should be displayed in the statusBar when heads upped. This is 6678 * usually just the app name, but may be different depending on the style. 6679 * 6680 * @param publicMode If true, return a text that is safe to display in public. 6681 * 6682 * @hide 6683 */ getHeadsUpStatusBarText(boolean publicMode)6684 public CharSequence getHeadsUpStatusBarText(boolean publicMode) { 6685 if (mStyle != null && !publicMode) { 6686 CharSequence text = mStyle.getHeadsUpStatusBarText(); 6687 if (!TextUtils.isEmpty(text)) { 6688 return text; 6689 } 6690 } 6691 return loadHeaderAppName(); 6692 } 6693 6694 /** 6695 * @return if this builder uses a template 6696 * 6697 * @hide 6698 */ usesTemplate()6699 public boolean usesTemplate() { 6700 return (mN.contentView == null && mN.headsUpContentView == null 6701 && mN.bigContentView == null) 6702 || styleDisplaysCustomViewInline(); 6703 } 6704 } 6705 6706 /** 6707 * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom 6708 * remote views. 6709 * 6710 * @hide 6711 */ reduceImageSizes(Context context)6712 void reduceImageSizes(Context context) { 6713 if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) { 6714 return; 6715 } 6716 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6717 if (mLargeIcon != null || largeIcon != null) { 6718 Resources resources = context.getResources(); 6719 Class<? extends Style> style = getNotificationStyle(); 6720 int maxSize = resources.getDimensionPixelSize(isLowRam 6721 ? R.dimen.notification_right_icon_size_low_ram 6722 : R.dimen.notification_right_icon_size); 6723 if (mLargeIcon != null) { 6724 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize); 6725 } 6726 if (largeIcon != null) { 6727 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize); 6728 } 6729 } 6730 reduceImageSizesForRemoteView(contentView, context, isLowRam); 6731 reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam); 6732 reduceImageSizesForRemoteView(bigContentView, context, isLowRam); 6733 extras.putBoolean(EXTRA_REDUCED_IMAGES, true); 6734 } 6735 reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6736 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, 6737 boolean isLowRam) { 6738 if (remoteView != null) { 6739 Resources resources = context.getResources(); 6740 int maxWidth = resources.getDimensionPixelSize(isLowRam 6741 ? R.dimen.notification_custom_view_max_image_width_low_ram 6742 : R.dimen.notification_custom_view_max_image_width); 6743 int maxHeight = resources.getDimensionPixelSize(isLowRam 6744 ? R.dimen.notification_custom_view_max_image_height_low_ram 6745 : R.dimen.notification_custom_view_max_image_height); 6746 remoteView.reduceImageSizes(maxWidth, maxHeight); 6747 } 6748 } 6749 6750 /** 6751 * @return whether this notification is a foreground service notification 6752 * @hide 6753 */ isForegroundService()6754 public boolean isForegroundService() { 6755 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 6756 } 6757 6758 /** 6759 * Describe whether this notification's content such that it should always display 6760 * immediately when tied to a foreground service, even if the system might generally 6761 * avoid showing the notifications for short-lived foreground service lifetimes. 6762 * 6763 * Immediate visibility of the Notification is indicated when: 6764 * <ul> 6765 * <li>The app specifically indicated it with 6766 * {@link Notification.Builder#setForegroundServiceBehavior(int) 6767 * setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li> 6768 * <li>It is a media notification or has an associated media session</li> 6769 * <li>It is a call or navigation notification</li> 6770 * <li>It provides additional action affordances</li> 6771 * </ul> 6772 * 6773 * If the app has specified 6774 * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)} 6775 * then this method will return {@code false} and notification visibility will be 6776 * deferred following the service's transition to the foreground state even in the 6777 * circumstances described above. 6778 * 6779 * @return whether this notification should be displayed immediately when 6780 * its associated service transitions to the foreground state 6781 * @hide 6782 */ 6783 @TestApi shouldShowForegroundImmediately()6784 public boolean shouldShowForegroundImmediately() { 6785 // Has the app demanded immediate display? 6786 if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) { 6787 return true; 6788 } 6789 6790 // Has the app demanded deferred display? 6791 if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) { 6792 return false; 6793 } 6794 6795 // We show these sorts of notifications immediately in the absence of 6796 // any explicit app declaration 6797 if (isMediaNotification() 6798 || CATEGORY_CALL.equals(category) 6799 || CATEGORY_NAVIGATION.equals(category) 6800 || (actions != null && actions.length > 0)) { 6801 return true; 6802 } 6803 6804 // No extenuating circumstances: defer visibility 6805 return false; 6806 } 6807 6808 /** 6809 * Has forced deferral for FGS purposes been specified? 6810 * @hide 6811 */ isForegroundDisplayForceDeferred()6812 public boolean isForegroundDisplayForceDeferred() { 6813 return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior; 6814 } 6815 6816 /** 6817 * @return the style class of this notification 6818 * @hide 6819 */ getNotificationStyle()6820 public Class<? extends Notification.Style> getNotificationStyle() { 6821 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6822 6823 if (!TextUtils.isEmpty(templateClass)) { 6824 return Notification.getNotificationStyleClass(templateClass); 6825 } 6826 return null; 6827 } 6828 6829 /** 6830 * @return whether the style of this notification is the one provided 6831 * @hide 6832 */ isStyle(@onNull Class<? extends Style> styleClass)6833 public boolean isStyle(@NonNull Class<? extends Style> styleClass) { 6834 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6835 return Objects.equals(templateClass, styleClass.getName()); 6836 } 6837 6838 /** 6839 * @return true if this notification is colorized *for the purposes of ranking*. If the 6840 * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual 6841 * appearance of the notification may not be "colorized". 6842 * 6843 * @hide 6844 */ isColorized()6845 public boolean isColorized() { 6846 return extras.getBoolean(EXTRA_COLORIZED) 6847 && (hasColorizedPermission() || isForegroundService()); 6848 } 6849 6850 /** 6851 * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS 6852 * permission. The permission is checked when a notification is enqueued. 6853 */ hasColorizedPermission()6854 private boolean hasColorizedPermission() { 6855 return (flags & Notification.FLAG_CAN_COLORIZE) != 0; 6856 } 6857 6858 /** 6859 * @return true if this is a media style notification with a media session 6860 * 6861 * @hide 6862 */ isMediaNotification()6863 public boolean isMediaNotification() { 6864 Class<? extends Style> style = getNotificationStyle(); 6865 boolean isMediaStyle = (MediaStyle.class.equals(style) 6866 || DecoratedMediaCustomViewStyle.class.equals(style)); 6867 6868 boolean hasMediaSession = (extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) != null 6869 && extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) 6870 instanceof MediaSession.Token); 6871 6872 return isMediaStyle && hasMediaSession; 6873 } 6874 6875 /** 6876 * @return true if this notification is showing as a bubble 6877 * 6878 * @hide 6879 */ isBubbleNotification()6880 public boolean isBubbleNotification() { 6881 return (flags & Notification.FLAG_BUBBLE) != 0; 6882 } 6883 hasLargeIcon()6884 private boolean hasLargeIcon() { 6885 return mLargeIcon != null || largeIcon != null; 6886 } 6887 6888 /** 6889 * @return true if the notification will show the time; false otherwise 6890 * @hide 6891 */ showsTime()6892 public boolean showsTime() { 6893 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 6894 } 6895 6896 /** 6897 * @return true if the notification will show a chronometer; false otherwise 6898 * @hide 6899 */ showsChronometer()6900 public boolean showsChronometer() { 6901 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 6902 } 6903 6904 /** 6905 * @return true if the notification has image 6906 */ hasImage()6907 public boolean hasImage() { 6908 if (isStyle(MessagingStyle.class) && extras != null) { 6909 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 6910 if (!ArrayUtils.isEmpty(messages)) { 6911 for (MessagingStyle.Message m : MessagingStyle.Message 6912 .getMessagesFromBundleArray(messages)) { 6913 if (m.getDataUri() != null 6914 && m.getDataMimeType() != null 6915 && m.getDataMimeType().startsWith("image/")) { 6916 return true; 6917 } 6918 } 6919 } 6920 } else if (hasLargeIcon()) { 6921 return true; 6922 } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 6923 return true; 6924 } 6925 return false; 6926 } 6927 6928 6929 /** 6930 * @removed 6931 */ 6932 @SystemApi getNotificationStyleClass(String templateClass)6933 public static Class<? extends Style> getNotificationStyleClass(String templateClass) { 6934 for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) { 6935 if (templateClass.equals(innerClass.getName())) { 6936 return innerClass; 6937 } 6938 } 6939 return null; 6940 } 6941 buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)6942 private static void buildCustomContentIntoTemplate(@NonNull Context context, 6943 @NonNull RemoteViews template, @Nullable RemoteViews customContent, 6944 @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) { 6945 int childIndex = -1; 6946 if (customContent != null) { 6947 // Need to clone customContent before adding, because otherwise it can no longer be 6948 // parceled independently of remoteViews. 6949 customContent = customContent.clone(); 6950 if (p.mHeaderless) { 6951 template.removeFromParent(R.id.notification_top_line); 6952 // We do not know how many lines ar emote view has, so we presume it has 2; this 6953 // ensures that we don't under-pad the content, which could lead to abuse, at the 6954 // cost of making single-line custom content over-padded. 6955 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */); 6956 } else { 6957 // also update the end margin to account for the large icon or expander 6958 Resources resources = context.getResources(); 6959 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column, 6960 resources.getDimension(R.dimen.notification_content_margin_end) 6961 / resources.getDisplayMetrics().density); 6962 } 6963 template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 6964 template.addView(R.id.notification_main_column, customContent, 0 /* index */); 6965 template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 6966 childIndex = 0; 6967 } 6968 template.setIntTag(R.id.notification_main_column, 6969 com.android.internal.R.id.notification_custom_view_index_tag, 6970 childIndex); 6971 } 6972 6973 /** 6974 * An object that can apply a rich notification style to a {@link Notification.Builder} 6975 * object. 6976 */ 6977 public static abstract class Style { 6978 6979 /** 6980 * The number of items allowed simulatanously in the remote input history. 6981 * @hide 6982 */ 6983 static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3; 6984 private CharSequence mBigContentTitle; 6985 6986 /** 6987 * @hide 6988 */ 6989 protected CharSequence mSummaryText = null; 6990 6991 /** 6992 * @hide 6993 */ 6994 protected boolean mSummaryTextSet = false; 6995 6996 protected Builder mBuilder; 6997 6998 /** 6999 * Overrides ContentTitle in the big form of the template. 7000 * This defaults to the value passed to setContentTitle(). 7001 */ internalSetBigContentTitle(CharSequence title)7002 protected void internalSetBigContentTitle(CharSequence title) { 7003 mBigContentTitle = title; 7004 } 7005 7006 /** 7007 * Set the first line of text after the detail section in the big form of the template. 7008 */ internalSetSummaryText(CharSequence cs)7009 protected void internalSetSummaryText(CharSequence cs) { 7010 mSummaryText = cs; 7011 mSummaryTextSet = true; 7012 } 7013 setBuilder(Builder builder)7014 public void setBuilder(Builder builder) { 7015 if (mBuilder != builder) { 7016 mBuilder = builder; 7017 if (mBuilder != null) { 7018 mBuilder.setStyle(this); 7019 } 7020 } 7021 } 7022 checkBuilder()7023 protected void checkBuilder() { 7024 if (mBuilder == null) { 7025 throw new IllegalArgumentException("Style requires a valid Builder object"); 7026 } 7027 } 7028 getStandardView(int layoutId)7029 protected RemoteViews getStandardView(int layoutId) { 7030 // TODO(jeffdq): set the view type based on the layout resource? 7031 StandardTemplateParams p = mBuilder.mParams.reset() 7032 .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED) 7033 .fillTextsFrom(mBuilder); 7034 return getStandardView(layoutId, p, null); 7035 } 7036 7037 7038 /** 7039 * Get the standard view for this style. 7040 * 7041 * @param layoutId The layout id to use. 7042 * @param p the params for this inflation. 7043 * @param result The result where template bind information is saved. 7044 * @return A remoteView for this style. 7045 * @hide 7046 */ getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7047 protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p, 7048 TemplateBindResult result) { 7049 checkBuilder(); 7050 7051 if (mBigContentTitle != null) { 7052 p.title = mBigContentTitle; 7053 } 7054 7055 return mBuilder.applyStandardTemplateWithActions(layoutId, p, result); 7056 } 7057 7058 /** 7059 * Construct a Style-specific RemoteViews for the collapsed notification layout. 7060 * The default implementation has nothing additional to add. 7061 * 7062 * @param increasedHeight true if this layout be created with an increased height. 7063 * @hide 7064 */ makeContentView(boolean increasedHeight)7065 public RemoteViews makeContentView(boolean increasedHeight) { 7066 return null; 7067 } 7068 7069 /** 7070 * Construct a Style-specific RemoteViews for the final big notification layout. 7071 * @hide 7072 */ makeBigContentView()7073 public RemoteViews makeBigContentView() { 7074 return null; 7075 } 7076 7077 /** 7078 * Construct a Style-specific RemoteViews for the final HUN layout. 7079 * 7080 * @param increasedHeight true if this layout be created with an increased height. 7081 * @hide 7082 */ makeHeadsUpContentView(boolean increasedHeight)7083 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7084 return null; 7085 } 7086 7087 /** 7088 * Apply any style-specific extras to this notification before shipping it out. 7089 * @hide 7090 */ addExtras(Bundle extras)7091 public void addExtras(Bundle extras) { 7092 if (mSummaryTextSet) { 7093 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 7094 } 7095 if (mBigContentTitle != null) { 7096 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 7097 } 7098 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 7099 } 7100 7101 /** 7102 * Reconstruct the internal state of this Style object from extras. 7103 * @hide 7104 */ restoreFromExtras(Bundle extras)7105 protected void restoreFromExtras(Bundle extras) { 7106 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 7107 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 7108 mSummaryTextSet = true; 7109 } 7110 if (extras.containsKey(EXTRA_TITLE_BIG)) { 7111 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 7112 } 7113 } 7114 7115 7116 /** 7117 * @hide 7118 */ buildStyled(Notification wip)7119 public Notification buildStyled(Notification wip) { 7120 addExtras(wip.extras); 7121 return wip; 7122 } 7123 7124 /** 7125 * @hide 7126 */ purgeResources()7127 public void purgeResources() {} 7128 7129 /** 7130 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 7131 * attached to. 7132 * 7133 * @return the fully constructed Notification. 7134 */ build()7135 public Notification build() { 7136 checkBuilder(); 7137 return mBuilder.build(); 7138 } 7139 7140 /** 7141 * @hide 7142 * @return Whether we should put the summary be put into the notification header 7143 */ hasSummaryInHeader()7144 public boolean hasSummaryInHeader() { 7145 return true; 7146 } 7147 7148 /** 7149 * @hide 7150 * @return Whether custom content views are displayed inline in the style 7151 */ displayCustomViewInline()7152 public boolean displayCustomViewInline() { 7153 return false; 7154 } 7155 7156 /** 7157 * Reduces the image sizes contained in this style. 7158 * 7159 * @hide 7160 */ reduceImageSizes(Context context)7161 public void reduceImageSizes(Context context) { 7162 } 7163 7164 /** 7165 * Validate that this style was properly composed. This is called at build time. 7166 * @hide 7167 */ validate(Context context)7168 public void validate(Context context) { 7169 } 7170 7171 /** 7172 * @hide 7173 */ areNotificationsVisiblyDifferent(Style other)7174 public abstract boolean areNotificationsVisiblyDifferent(Style other); 7175 7176 /** 7177 * @return the text that should be displayed in the statusBar when heads-upped. 7178 * If {@code null} is returned, the default implementation will be used. 7179 * 7180 * @hide 7181 */ getHeadsUpStatusBarText()7182 public CharSequence getHeadsUpStatusBarText() { 7183 return null; 7184 } 7185 } 7186 7187 /** 7188 * Helper class for generating large-format notifications that include a large image attachment. 7189 * 7190 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 7191 * <pre class="prettyprint"> 7192 * Notification notif = new Notification.Builder(mContext) 7193 * .setContentTitle("New photo from " + sender.toString()) 7194 * .setContentText(subject) 7195 * .setSmallIcon(R.drawable.new_post) 7196 * .setLargeIcon(aBitmap) 7197 * .setStyle(new Notification.BigPictureStyle() 7198 * .bigPicture(aBigBitmap)) 7199 * .build(); 7200 * </pre> 7201 * 7202 * @see Notification#bigContentView 7203 */ 7204 public static class BigPictureStyle extends Style { 7205 private Icon mPictureIcon; 7206 private Icon mBigLargeIcon; 7207 private boolean mBigLargeIconSet = false; 7208 private CharSequence mPictureContentDescription; 7209 private boolean mShowBigPictureWhenCollapsed; 7210 BigPictureStyle()7211 public BigPictureStyle() { 7212 } 7213 7214 /** 7215 * @deprecated use {@code BigPictureStyle()}. 7216 */ 7217 @Deprecated BigPictureStyle(Builder builder)7218 public BigPictureStyle(Builder builder) { 7219 setBuilder(builder); 7220 } 7221 7222 /** 7223 * Overrides ContentTitle in the big form of the template. 7224 * This defaults to the value passed to setContentTitle(). 7225 */ 7226 @NonNull setBigContentTitle(@ullable CharSequence title)7227 public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) { 7228 internalSetBigContentTitle(safeCharSequence(title)); 7229 return this; 7230 } 7231 7232 /** 7233 * Set the first line of text after the detail section in the big form of the template. 7234 */ 7235 @NonNull setSummaryText(@ullable CharSequence cs)7236 public BigPictureStyle setSummaryText(@Nullable CharSequence cs) { 7237 internalSetSummaryText(safeCharSequence(cs)); 7238 return this; 7239 } 7240 7241 /** 7242 * Set the content description of the big picture. 7243 */ 7244 @NonNull setContentDescription( @ullable CharSequence contentDescription)7245 public BigPictureStyle setContentDescription( 7246 @Nullable CharSequence contentDescription) { 7247 mPictureContentDescription = contentDescription; 7248 return this; 7249 } 7250 7251 /** 7252 * @hide 7253 */ 7254 @Nullable getBigPicture()7255 public Icon getBigPicture() { 7256 if (mPictureIcon != null) { 7257 return mPictureIcon; 7258 } 7259 return null; 7260 } 7261 7262 /** 7263 * Provide the bitmap to be used as the payload for the BigPicture notification. 7264 */ 7265 @NonNull bigPicture(@ullable Bitmap b)7266 public BigPictureStyle bigPicture(@Nullable Bitmap b) { 7267 mPictureIcon = b == null ? null : Icon.createWithBitmap(b); 7268 return this; 7269 } 7270 7271 /** 7272 * Provide the content Uri to be used as the payload for the BigPicture notification. 7273 */ 7274 @NonNull bigPicture(@ullable Icon icon)7275 public BigPictureStyle bigPicture(@Nullable Icon icon) { 7276 mPictureIcon = icon; 7277 return this; 7278 } 7279 7280 /** 7281 * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and 7282 * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed 7283 * state of this notification. 7284 */ 7285 @NonNull showBigPictureWhenCollapsed(boolean show)7286 public BigPictureStyle showBigPictureWhenCollapsed(boolean show) { 7287 mShowBigPictureWhenCollapsed = show; 7288 return this; 7289 } 7290 7291 /** 7292 * Override the large icon when the big notification is shown. 7293 */ 7294 @NonNull bigLargeIcon(@ullable Bitmap b)7295 public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) { 7296 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 7297 } 7298 7299 /** 7300 * Override the large icon when the big notification is shown. 7301 */ 7302 @NonNull bigLargeIcon(@ullable Icon icon)7303 public BigPictureStyle bigLargeIcon(@Nullable Icon icon) { 7304 mBigLargeIconSet = true; 7305 mBigLargeIcon = icon; 7306 return this; 7307 } 7308 7309 /** @hide */ 7310 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 7311 7312 /** 7313 * @hide 7314 */ 7315 @Override purgeResources()7316 public void purgeResources() { 7317 super.purgeResources(); 7318 if (mPictureIcon != null) { 7319 mPictureIcon.convertToAshmem(); 7320 } 7321 if (mBigLargeIcon != null) { 7322 mBigLargeIcon.convertToAshmem(); 7323 } 7324 } 7325 7326 /** 7327 * @hide 7328 */ 7329 @Override reduceImageSizes(Context context)7330 public void reduceImageSizes(Context context) { 7331 super.reduceImageSizes(context); 7332 Resources resources = context.getResources(); 7333 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 7334 if (mPictureIcon != null) { 7335 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam 7336 ? R.dimen.notification_big_picture_max_height_low_ram 7337 : R.dimen.notification_big_picture_max_height); 7338 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam 7339 ? R.dimen.notification_big_picture_max_width_low_ram 7340 : R.dimen.notification_big_picture_max_width); 7341 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight); 7342 } 7343 if (mBigLargeIcon != null) { 7344 int rightIconSize = resources.getDimensionPixelSize(isLowRam 7345 ? R.dimen.notification_right_icon_size_low_ram 7346 : R.dimen.notification_right_icon_size); 7347 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 7348 } 7349 } 7350 7351 /** 7352 * @hide 7353 */ 7354 @Override makeContentView(boolean increasedHeight)7355 public RemoteViews makeContentView(boolean increasedHeight) { 7356 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7357 return super.makeContentView(increasedHeight); 7358 } 7359 7360 StandardTemplateParams p = mBuilder.mParams.reset() 7361 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 7362 .fillTextsFrom(mBuilder) 7363 .promotedPicture(mPictureIcon); 7364 return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */); 7365 } 7366 7367 /** 7368 * @hide 7369 */ 7370 @Override makeHeadsUpContentView(boolean increasedHeight)7371 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7372 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7373 return super.makeHeadsUpContentView(increasedHeight); 7374 } 7375 7376 StandardTemplateParams p = mBuilder.mParams.reset() 7377 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 7378 .fillTextsFrom(mBuilder) 7379 .promotedPicture(mPictureIcon); 7380 return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */); 7381 } 7382 7383 /** 7384 * @hide 7385 */ makeBigContentView()7386 public RemoteViews makeBigContentView() { 7387 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 7388 // This covers the following cases: 7389 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 7390 // mN.mLargeIcon 7391 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 7392 Icon oldLargeIcon = null; 7393 Bitmap largeIconLegacy = null; 7394 if (mBigLargeIconSet) { 7395 oldLargeIcon = mBuilder.mN.mLargeIcon; 7396 mBuilder.mN.mLargeIcon = mBigLargeIcon; 7397 // The legacy largeIcon might not allow us to clear the image, as it's taken in 7398 // replacement if the other one is null. Because we're restoring these legacy icons 7399 // for old listeners, this is in general non-null. 7400 largeIconLegacy = mBuilder.mN.largeIcon; 7401 mBuilder.mN.largeIcon = null; 7402 } 7403 7404 StandardTemplateParams p = mBuilder.mParams.reset() 7405 .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder); 7406 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), 7407 p, null /* result */); 7408 if (mSummaryTextSet) { 7409 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans( 7410 mBuilder.processLegacyText(mSummaryText))); 7411 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); 7412 contentView.setViewVisibility(R.id.text, View.VISIBLE); 7413 } 7414 7415 if (mBigLargeIconSet) { 7416 mBuilder.mN.mLargeIcon = oldLargeIcon; 7417 mBuilder.mN.largeIcon = largeIconLegacy; 7418 } 7419 7420 contentView.setImageViewIcon(R.id.big_picture, mPictureIcon); 7421 7422 if (mPictureContentDescription != null) { 7423 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription); 7424 } 7425 7426 return contentView; 7427 } 7428 7429 /** 7430 * @hide 7431 */ addExtras(Bundle extras)7432 public void addExtras(Bundle extras) { 7433 super.addExtras(extras); 7434 7435 if (mBigLargeIconSet) { 7436 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 7437 } 7438 if (mPictureContentDescription != null) { 7439 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION, 7440 mPictureContentDescription); 7441 } 7442 extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); 7443 7444 // If the icon contains a bitmap, use the old extra so that listeners which look for 7445 // that extra can still find the picture. Don't include the new extra in that case, 7446 // to avoid duplicating data. 7447 if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) { 7448 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); 7449 extras.putParcelable(EXTRA_PICTURE_ICON, null); 7450 } else { 7451 extras.putParcelable(EXTRA_PICTURE, null); 7452 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); 7453 } 7454 } 7455 7456 /** 7457 * @hide 7458 */ 7459 @Override restoreFromExtras(Bundle extras)7460 protected void restoreFromExtras(Bundle extras) { 7461 super.restoreFromExtras(extras); 7462 7463 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 7464 mBigLargeIconSet = true; 7465 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG); 7466 } 7467 7468 if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) { 7469 mPictureContentDescription = 7470 extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION); 7471 } 7472 7473 mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED); 7474 7475 mPictureIcon = getPictureIcon(extras); 7476 } 7477 7478 /** @hide */ 7479 @Nullable getPictureIcon(@ullable Bundle extras)7480 public static Icon getPictureIcon(@Nullable Bundle extras) { 7481 if (extras == null) return null; 7482 // When this style adds a picture, we only add one of the keys. If both were added, 7483 // it would most likely be a legacy app trying to override the picture in some way. 7484 // Because of that case it's better to give precedence to the legacy field. 7485 Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE); 7486 if (bitmapPicture != null) { 7487 return Icon.createWithBitmap(bitmapPicture); 7488 } else { 7489 return extras.getParcelable(EXTRA_PICTURE_ICON); 7490 } 7491 } 7492 7493 /** 7494 * @hide 7495 */ 7496 @Override hasSummaryInHeader()7497 public boolean hasSummaryInHeader() { 7498 return false; 7499 } 7500 7501 /** 7502 * @hide 7503 * Note that we aren't actually comparing the contents of the bitmaps here, so this 7504 * is only doing a cursory inspection. Bitmaps of equal size will appear the same. 7505 */ 7506 @Override areNotificationsVisiblyDifferent(Style other)7507 public boolean areNotificationsVisiblyDifferent(Style other) { 7508 if (other == null || getClass() != other.getClass()) { 7509 return true; 7510 } 7511 BigPictureStyle otherS = (BigPictureStyle) other; 7512 return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture()); 7513 } 7514 areIconsObviouslyDifferent(Icon a, Icon b)7515 private static boolean areIconsObviouslyDifferent(Icon a, Icon b) { 7516 if (a == b) { 7517 return false; 7518 } 7519 if (a == null || b == null) { 7520 return true; 7521 } 7522 if (a.sameAs(b)) { 7523 return false; 7524 } 7525 final int aType = a.getType(); 7526 if (aType != b.getType()) { 7527 return true; 7528 } 7529 if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) { 7530 final Bitmap aBitmap = a.getBitmap(); 7531 final Bitmap bBitmap = b.getBitmap(); 7532 return aBitmap.getWidth() != bBitmap.getWidth() 7533 || aBitmap.getHeight() != bBitmap.getHeight() 7534 || aBitmap.getConfig() != bBitmap.getConfig() 7535 || aBitmap.getGenerationId() != bBitmap.getGenerationId(); 7536 } 7537 return true; 7538 } 7539 } 7540 7541 /** 7542 * Helper class for generating large-format notifications that include a lot of text. 7543 * 7544 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 7545 * <pre class="prettyprint"> 7546 * Notification notif = new Notification.Builder(mContext) 7547 * .setContentTitle("New mail from " + sender.toString()) 7548 * .setContentText(subject) 7549 * .setSmallIcon(R.drawable.new_mail) 7550 * .setLargeIcon(aBitmap) 7551 * .setStyle(new Notification.BigTextStyle() 7552 * .bigText(aVeryLongString)) 7553 * .build(); 7554 * </pre> 7555 * 7556 * @see Notification#bigContentView 7557 */ 7558 public static class BigTextStyle extends Style { 7559 7560 private CharSequence mBigText; 7561 BigTextStyle()7562 public BigTextStyle() { 7563 } 7564 7565 /** 7566 * @deprecated use {@code BigTextStyle()}. 7567 */ 7568 @Deprecated BigTextStyle(Builder builder)7569 public BigTextStyle(Builder builder) { 7570 setBuilder(builder); 7571 } 7572 7573 /** 7574 * Overrides ContentTitle in the big form of the template. 7575 * This defaults to the value passed to setContentTitle(). 7576 */ setBigContentTitle(CharSequence title)7577 public BigTextStyle setBigContentTitle(CharSequence title) { 7578 internalSetBigContentTitle(safeCharSequence(title)); 7579 return this; 7580 } 7581 7582 /** 7583 * Set the first line of text after the detail section in the big form of the template. 7584 */ setSummaryText(CharSequence cs)7585 public BigTextStyle setSummaryText(CharSequence cs) { 7586 internalSetSummaryText(safeCharSequence(cs)); 7587 return this; 7588 } 7589 7590 /** 7591 * Provide the longer text to be displayed in the big form of the 7592 * template in place of the content text. 7593 */ bigText(CharSequence cs)7594 public BigTextStyle bigText(CharSequence cs) { 7595 mBigText = safeCharSequence(cs); 7596 return this; 7597 } 7598 7599 /** 7600 * @hide 7601 */ getBigText()7602 public CharSequence getBigText() { 7603 return mBigText; 7604 } 7605 7606 /** 7607 * @hide 7608 */ addExtras(Bundle extras)7609 public void addExtras(Bundle extras) { 7610 super.addExtras(extras); 7611 7612 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 7613 } 7614 7615 /** 7616 * @hide 7617 */ 7618 @Override restoreFromExtras(Bundle extras)7619 protected void restoreFromExtras(Bundle extras) { 7620 super.restoreFromExtras(extras); 7621 7622 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 7623 } 7624 7625 /** 7626 * @param increasedHeight true if this layout be created with an increased height. 7627 * 7628 * @hide 7629 */ 7630 @Override makeContentView(boolean increasedHeight)7631 public RemoteViews makeContentView(boolean increasedHeight) { 7632 if (increasedHeight) { 7633 ArrayList<Action> originalActions = mBuilder.mActions; 7634 mBuilder.mActions = new ArrayList<>(); 7635 RemoteViews remoteViews = makeBigContentView(); 7636 mBuilder.mActions = originalActions; 7637 return remoteViews; 7638 } 7639 return super.makeContentView(increasedHeight); 7640 } 7641 7642 /** 7643 * @hide 7644 */ 7645 @Override makeHeadsUpContentView(boolean increasedHeight)7646 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7647 if (increasedHeight && mBuilder.mActions.size() > 0) { 7648 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP? 7649 return makeBigContentView(); 7650 } 7651 return super.makeHeadsUpContentView(increasedHeight); 7652 } 7653 7654 /** 7655 * @hide 7656 */ makeBigContentView()7657 public RemoteViews makeBigContentView() { 7658 StandardTemplateParams p = mBuilder.mParams.reset() 7659 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 7660 .allowTextWithProgress(true) 7661 .textViewId(R.id.big_text) 7662 .fillTextsFrom(mBuilder); 7663 7664 // Replace the text with the big text, but only if the big text is not empty. 7665 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 7666 if (!TextUtils.isEmpty(bigTextText)) { 7667 p.text(bigTextText); 7668 } 7669 7670 return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */); 7671 } 7672 7673 /** 7674 * @hide 7675 * Spans are ignored when comparing text for visual difference. 7676 */ 7677 @Override areNotificationsVisiblyDifferent(Style other)7678 public boolean areNotificationsVisiblyDifferent(Style other) { 7679 if (other == null || getClass() != other.getClass()) { 7680 return true; 7681 } 7682 BigTextStyle newS = (BigTextStyle) other; 7683 return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText())); 7684 } 7685 7686 } 7687 7688 /** 7689 * Helper class for generating large-format notifications that include multiple back-and-forth 7690 * messages of varying types between any number of people. 7691 * 7692 * <p> 7693 * If the platform does not provide large-format notifications, this method has no effect. The 7694 * user will always see the normal notification view. 7695 * 7696 * <p> 7697 * If the app is targeting Android P and above, it is required to use the {@link Person} 7698 * class in order to get an optimal rendering of the notification and its avatars. For 7699 * conversations involving multiple people, the app should also make sure that it marks the 7700 * conversation as a group with {@link #setGroupConversation(boolean)}. 7701 * 7702 * <p> 7703 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. 7704 * Here's an example of how this may be used: 7705 * <pre class="prettyprint"> 7706 * 7707 * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build(); 7708 * MessagingStyle style = new MessagingStyle(user) 7709 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson()) 7710 * .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson()) 7711 * .setGroupConversation(hasMultiplePeople()); 7712 * 7713 * Notification noti = new Notification.Builder() 7714 * .setContentTitle("2 new messages with " + sender.toString()) 7715 * .setContentText(subject) 7716 * .setSmallIcon(R.drawable.new_message) 7717 * .setLargeIcon(aBitmap) 7718 * .setStyle(style) 7719 * .build(); 7720 * </pre> 7721 */ 7722 public static class MessagingStyle extends Style { 7723 7724 /** 7725 * The maximum number of messages that will be retained in the Notification itself (the 7726 * number displayed is up to the platform). 7727 */ 7728 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 7729 7730 7731 /** @hide */ 7732 public static final int CONVERSATION_TYPE_LEGACY = 0; 7733 /** @hide */ 7734 public static final int CONVERSATION_TYPE_NORMAL = 1; 7735 /** @hide */ 7736 public static final int CONVERSATION_TYPE_IMPORTANT = 2; 7737 7738 /** @hide */ 7739 @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = { 7740 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT 7741 }) 7742 @Retention(RetentionPolicy.SOURCE) 7743 public @interface ConversationType {} 7744 7745 @NonNull Person mUser; 7746 @Nullable CharSequence mConversationTitle; 7747 @Nullable Icon mShortcutIcon; 7748 List<Message> mMessages = new ArrayList<>(); 7749 List<Message> mHistoricMessages = new ArrayList<>(); 7750 boolean mIsGroupConversation; 7751 @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; 7752 int mUnreadMessageCount; 7753 MessagingStyle()7754 MessagingStyle() { 7755 } 7756 7757 /** 7758 * @param userDisplayName Required - the name to be displayed for any replies sent by the 7759 * user before the posting app reposts the notification with those messages after they've 7760 * been actually sent and in previous messages sent by the user added in 7761 * {@link #addMessage(Notification.MessagingStyle.Message)} 7762 * 7763 * @deprecated use {@code MessagingStyle(Person)} 7764 */ MessagingStyle(@onNull CharSequence userDisplayName)7765 public MessagingStyle(@NonNull CharSequence userDisplayName) { 7766 this(new Person.Builder().setName(userDisplayName).build()); 7767 } 7768 7769 /** 7770 * @param user Required - The person displayed for any messages that are sent by the 7771 * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} 7772 * who don't have a Person associated with it will be displayed as if they were sent 7773 * by this user. The user also needs to have a valid name associated with it, which will 7774 * be enforced starting in Android P. 7775 */ MessagingStyle(@onNull Person user)7776 public MessagingStyle(@NonNull Person user) { 7777 mUser = user; 7778 } 7779 7780 /** 7781 * Validate that this style was properly composed. This is called at build time. 7782 * @hide 7783 */ 7784 @Override validate(Context context)7785 public void validate(Context context) { 7786 super.validate(context); 7787 if (context.getApplicationInfo().targetSdkVersion 7788 >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { 7789 throw new RuntimeException("User must be valid and have a name."); 7790 } 7791 } 7792 7793 /** 7794 * @return the text that should be displayed in the statusBar when heads upped. 7795 * If {@code null} is returned, the default implementation will be used. 7796 * 7797 * @hide 7798 */ 7799 @Override getHeadsUpStatusBarText()7800 public CharSequence getHeadsUpStatusBarText() { 7801 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7802 ? super.mBigContentTitle 7803 : mConversationTitle; 7804 if (mConversationType == CONVERSATION_TYPE_LEGACY 7805 && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { 7806 return conversationTitle; 7807 } 7808 return null; 7809 } 7810 7811 /** 7812 * @return the user to be displayed for any replies sent by the user 7813 */ 7814 @NonNull getUser()7815 public Person getUser() { 7816 return mUser; 7817 } 7818 7819 /** 7820 * Returns the name to be displayed for any replies sent by the user 7821 * 7822 * @deprecated use {@link #getUser()} instead 7823 */ getUserDisplayName()7824 public CharSequence getUserDisplayName() { 7825 return mUser.getName(); 7826 } 7827 7828 /** 7829 * Sets the title to be displayed on this conversation. May be set to {@code null}. 7830 * 7831 * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a 7832 * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this 7833 * case, {@link ShortcutInfo#getLongLabel()} (or, if missing, 7834 * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title 7835 * instead. 7836 * 7837 * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your 7838 * application's target version is less than {@link Build.VERSION_CODES#P}, setting a 7839 * conversation title to a non-null value will make {@link #isGroupConversation()} return 7840 * {@code true} and passing {@code null} will make it return {@code false}. In 7841 * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} 7842 * to set group conversation status. 7843 * 7844 * @param conversationTitle Title displayed for this conversation 7845 * @return this object for method chaining 7846 */ setConversationTitle(@ullable CharSequence conversationTitle)7847 public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { 7848 mConversationTitle = conversationTitle; 7849 return this; 7850 } 7851 7852 /** 7853 * Return the title to be displayed on this conversation. May return {@code null}. 7854 */ 7855 @Nullable getConversationTitle()7856 public CharSequence getConversationTitle() { 7857 return mConversationTitle; 7858 } 7859 7860 /** 7861 * Sets the icon to be displayed on the conversation, derived from the shortcutId. 7862 * 7863 * @hide 7864 */ setShortcutIcon(@ullable Icon conversationIcon)7865 public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) { 7866 mShortcutIcon = conversationIcon; 7867 return this; 7868 } 7869 7870 /** 7871 * Return the icon to be displayed on this conversation, derived from the shortcutId. May 7872 * return {@code null}. 7873 * 7874 * @hide 7875 */ 7876 @Nullable getShortcutIcon()7877 public Icon getShortcutIcon() { 7878 return mShortcutIcon; 7879 } 7880 7881 /** 7882 * Sets the conversation type of this MessageStyle notification. 7883 * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R, 7884 * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and 7885 * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments. 7886 * 7887 * @hide 7888 */ setConversationType(@onversationType int conversationType)7889 public MessagingStyle setConversationType(@ConversationType int conversationType) { 7890 mConversationType = conversationType; 7891 return this; 7892 } 7893 7894 /** @hide */ 7895 @ConversationType getConversationType()7896 public int getConversationType() { 7897 return mConversationType; 7898 } 7899 7900 /** @hide */ getUnreadMessageCount()7901 public int getUnreadMessageCount() { 7902 return mUnreadMessageCount; 7903 } 7904 7905 /** @hide */ setUnreadMessageCount(int unreadMessageCount)7906 public MessagingStyle setUnreadMessageCount(int unreadMessageCount) { 7907 mUnreadMessageCount = unreadMessageCount; 7908 return this; 7909 } 7910 7911 /** 7912 * Adds a message for display by this notification. Convenience call for a simple 7913 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7914 * @param text A {@link CharSequence} to be displayed as the message content 7915 * @param timestamp Time at which the message arrived 7916 * @param sender A {@link CharSequence} to be used for displaying the name of the 7917 * sender. Should be <code>null</code> for messages by the current user, in which case 7918 * the platform will insert {@link #getUserDisplayName()}. 7919 * Should be unique amongst all individuals in the conversation, and should be 7920 * consistent during re-posts of the notification. 7921 * 7922 * @see Message#Message(CharSequence, long, CharSequence) 7923 * 7924 * @return this object for method chaining 7925 * 7926 * @deprecated use {@link #addMessage(CharSequence, long, Person)} 7927 */ addMessage(CharSequence text, long timestamp, CharSequence sender)7928 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 7929 return addMessage(text, timestamp, 7930 sender == null ? null : new Person.Builder().setName(sender).build()); 7931 } 7932 7933 /** 7934 * Adds a message for display by this notification. Convenience call for a simple 7935 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 7936 * @param text A {@link CharSequence} to be displayed as the message content 7937 * @param timestamp Time at which the message arrived 7938 * @param sender The {@link Person} who sent the message. 7939 * Should be <code>null</code> for messages by the current user, in which case 7940 * the platform will insert the user set in {@code MessagingStyle(Person)}. 7941 * 7942 * @see Message#Message(CharSequence, long, CharSequence) 7943 * 7944 * @return this object for method chaining 7945 */ addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)7946 public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, 7947 @Nullable Person sender) { 7948 return addMessage(new Message(text, timestamp, sender)); 7949 } 7950 7951 /** 7952 * Adds a {@link Message} for display in this notification. 7953 * 7954 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7955 * the newest last. 7956 * 7957 * @param message The {@link Message} to be displayed 7958 * @return this object for method chaining 7959 */ addMessage(Message message)7960 public MessagingStyle addMessage(Message message) { 7961 mMessages.add(message); 7962 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7963 mMessages.remove(0); 7964 } 7965 return this; 7966 } 7967 7968 /** 7969 * Adds a {@link Message} for historic context in this notification. 7970 * 7971 * <p>Messages should be added as historic if they are not the main subject of the 7972 * notification but may give context to a conversation. The system may choose to present 7973 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}. 7974 * 7975 * <p>The messages should be added in chronologic order, i.e. the oldest first, 7976 * the newest last. 7977 * 7978 * @param message The historic {@link Message} to be added 7979 * @return this object for method chaining 7980 */ addHistoricMessage(Message message)7981 public MessagingStyle addHistoricMessage(Message message) { 7982 mHistoricMessages.add(message); 7983 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 7984 mHistoricMessages.remove(0); 7985 } 7986 return this; 7987 } 7988 7989 /** 7990 * Gets the list of {@code Message} objects that represent the notification 7991 */ getMessages()7992 public List<Message> getMessages() { 7993 return mMessages; 7994 } 7995 7996 /** 7997 * Gets the list of historic {@code Message}s in the notification. 7998 */ getHistoricMessages()7999 public List<Message> getHistoricMessages() { 8000 return mHistoricMessages; 8001 } 8002 8003 /** 8004 * Sets whether this conversation notification represents a group. If the app is targeting 8005 * Android P, this is required if the app wants to display the largeIcon set with 8006 * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden. 8007 * 8008 * @param isGroupConversation {@code true} if the conversation represents a group, 8009 * {@code false} otherwise. 8010 * @return this object for method chaining 8011 */ setGroupConversation(boolean isGroupConversation)8012 public MessagingStyle setGroupConversation(boolean isGroupConversation) { 8013 mIsGroupConversation = isGroupConversation; 8014 return this; 8015 } 8016 8017 /** 8018 * Returns {@code true} if this notification represents a group conversation, otherwise 8019 * {@code false}. 8020 * 8021 * <p> If the application that generated this {@link MessagingStyle} targets an SDK version 8022 * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or 8023 * not the conversation title is set; returning {@code true} if the conversation title is 8024 * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, 8025 * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for 8026 * named, non-group conversations. 8027 * 8028 * @see #setConversationTitle(CharSequence) 8029 */ isGroupConversation()8030 public boolean isGroupConversation() { 8031 // When target SDK version is < P, a non-null conversation title dictates if this is 8032 // as group conversation. 8033 if (mBuilder != null 8034 && mBuilder.mContext.getApplicationInfo().targetSdkVersion 8035 < Build.VERSION_CODES.P) { 8036 return mConversationTitle != null; 8037 } 8038 8039 return mIsGroupConversation; 8040 } 8041 8042 /** 8043 * @hide 8044 */ 8045 @Override addExtras(Bundle extras)8046 public void addExtras(Bundle extras) { 8047 super.addExtras(extras); 8048 if (mUser != null) { 8049 // For legacy usages 8050 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); 8051 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); 8052 } 8053 if (mConversationTitle != null) { 8054 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 8055 } 8056 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES, 8057 Message.getBundleArrayForMessages(mMessages)); 8058 } 8059 if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, 8060 Message.getBundleArrayForMessages(mHistoricMessages)); 8061 } 8062 if (mShortcutIcon != null) { 8063 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon); 8064 } 8065 extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount); 8066 8067 fixTitleAndTextExtras(extras); 8068 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); 8069 } 8070 fixTitleAndTextExtras(Bundle extras)8071 private void fixTitleAndTextExtras(Bundle extras) { 8072 Message m = findLatestIncomingMessage(); 8073 CharSequence text = (m == null) ? null : m.mText; 8074 CharSequence sender = m == null ? null 8075 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 8076 ? mUser.getName() : m.mSender.getName(); 8077 CharSequence title; 8078 if (!TextUtils.isEmpty(mConversationTitle)) { 8079 if (!TextUtils.isEmpty(sender)) { 8080 BidiFormatter bidi = BidiFormatter.getInstance(); 8081 title = mBuilder.mContext.getString( 8082 com.android.internal.R.string.notification_messaging_title_template, 8083 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); 8084 } else { 8085 title = mConversationTitle; 8086 } 8087 } else { 8088 title = sender; 8089 } 8090 8091 if (title != null) { 8092 extras.putCharSequence(EXTRA_TITLE, title); 8093 } 8094 if (text != null) { 8095 extras.putCharSequence(EXTRA_TEXT, text); 8096 } 8097 } 8098 8099 /** 8100 * @hide 8101 */ 8102 @Override restoreFromExtras(Bundle extras)8103 protected void restoreFromExtras(Bundle extras) { 8104 super.restoreFromExtras(extras); 8105 8106 mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON); 8107 if (mUser == null) { 8108 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 8109 mUser = new Person.Builder().setName(displayName).build(); 8110 } 8111 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 8112 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 8113 mMessages = Message.getMessagesFromBundleArray(messages); 8114 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 8115 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); 8116 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); 8117 mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 8118 mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON); 8119 } 8120 8121 /** 8122 * @hide 8123 */ 8124 @Override makeContentView(boolean increasedHeight)8125 public RemoteViews makeContentView(boolean increasedHeight) { 8126 // All messaging templates contain the actions 8127 ArrayList<Action> originalActions = mBuilder.mActions; 8128 try { 8129 mBuilder.mActions = new ArrayList<>(); 8130 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL); 8131 } finally { 8132 mBuilder.mActions = originalActions; 8133 } 8134 } 8135 8136 /** 8137 * @hide 8138 * Spans are ignored when comparing text for visual difference. 8139 */ 8140 @Override areNotificationsVisiblyDifferent(Style other)8141 public boolean areNotificationsVisiblyDifferent(Style other) { 8142 if (other == null || getClass() != other.getClass()) { 8143 return true; 8144 } 8145 MessagingStyle newS = (MessagingStyle) other; 8146 List<MessagingStyle.Message> oldMs = getMessages(); 8147 List<MessagingStyle.Message> newMs = newS.getMessages(); 8148 8149 if (oldMs == null || newMs == null) { 8150 newMs = new ArrayList<>(); 8151 } 8152 8153 int n = oldMs.size(); 8154 if (n != newMs.size()) { 8155 return true; 8156 } 8157 for (int i = 0; i < n; i++) { 8158 MessagingStyle.Message oldM = oldMs.get(i); 8159 MessagingStyle.Message newM = newMs.get(i); 8160 if (!Objects.equals( 8161 String.valueOf(oldM.getText()), 8162 String.valueOf(newM.getText()))) { 8163 return true; 8164 } 8165 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { 8166 return true; 8167 } 8168 String oldSender = String.valueOf(oldM.getSenderPerson() == null 8169 ? oldM.getSender() 8170 : oldM.getSenderPerson().getName()); 8171 String newSender = String.valueOf(newM.getSenderPerson() == null 8172 ? newM.getSender() 8173 : newM.getSenderPerson().getName()); 8174 if (!Objects.equals(oldSender, newSender)) { 8175 return true; 8176 } 8177 8178 String oldKey = oldM.getSenderPerson() == null 8179 ? null : oldM.getSenderPerson().getKey(); 8180 String newKey = newM.getSenderPerson() == null 8181 ? null : newM.getSenderPerson().getKey(); 8182 if (!Objects.equals(oldKey, newKey)) { 8183 return true; 8184 } 8185 // Other fields (like timestamp) intentionally excluded 8186 } 8187 return false; 8188 } 8189 findLatestIncomingMessage()8190 private Message findLatestIncomingMessage() { 8191 return findLatestIncomingMessage(mMessages); 8192 } 8193 8194 /** 8195 * @hide 8196 */ 8197 @Nullable findLatestIncomingMessage( List<Message> messages)8198 public static Message findLatestIncomingMessage( 8199 List<Message> messages) { 8200 for (int i = messages.size() - 1; i >= 0; i--) { 8201 Message m = messages.get(i); 8202 // Incoming messages have a non-empty sender. 8203 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { 8204 return m; 8205 } 8206 } 8207 if (!messages.isEmpty()) { 8208 // No incoming messages, fall back to outgoing message 8209 return messages.get(messages.size() - 1); 8210 } 8211 return null; 8212 } 8213 8214 /** 8215 * @hide 8216 */ 8217 @Override makeBigContentView()8218 public RemoteViews makeBigContentView() { 8219 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG); 8220 } 8221 8222 /** 8223 * Create a messaging layout. 8224 * 8225 * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG, 8226 * VIEW_TYPE_HEADS_UP 8227 * @return the created remoteView. 8228 */ 8229 @NonNull makeMessagingView(int viewType)8230 private RemoteViews makeMessagingView(int viewType) { 8231 boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG; 8232 boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL; 8233 boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 8234 boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; 8235 boolean isHeaderless = !isConversationLayout && isCollapsed; 8236 8237 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 8238 ? super.mBigContentTitle 8239 : mConversationTitle; 8240 boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion 8241 >= Build.VERSION_CODES.P; 8242 boolean isOneToOne; 8243 CharSequence nameReplacement = null; 8244 if (!atLeastP) { 8245 isOneToOne = TextUtils.isEmpty(conversationTitle); 8246 if (hasOnlyWhiteSpaceSenders()) { 8247 isOneToOne = true; 8248 nameReplacement = conversationTitle; 8249 conversationTitle = null; 8250 } 8251 } else { 8252 isOneToOne = !isGroupConversation(); 8253 } 8254 if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) { 8255 conversationTitle = getOtherPersonName(); 8256 } 8257 8258 Icon largeIcon = mBuilder.mN.mLargeIcon; 8259 TemplateBindResult bindResult = new TemplateBindResult(); 8260 StandardTemplateParams p = mBuilder.mParams.reset() 8261 .viewType(viewType) 8262 .highlightExpander(isConversationLayout) 8263 .hideProgress(true) 8264 .title(isHeaderless ? conversationTitle : null) 8265 .text(null) 8266 .hideLeftIcon(isOneToOne) 8267 .hideRightIcon(hideRightIcons || isOneToOne) 8268 .headerTextSecondary(isHeaderless ? null : conversationTitle); 8269 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 8270 isConversationLayout 8271 ? mBuilder.getConversationLayoutResource() 8272 : isCollapsed 8273 ? mBuilder.getMessagingLayoutResource() 8274 : mBuilder.getBigMessagingLayoutResource(), 8275 p, 8276 bindResult); 8277 if (isConversationLayout) { 8278 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p); 8279 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 8280 } 8281 8282 addExtras(mBuilder.mN.extras); 8283 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 8284 mBuilder.getSmallIconColor(p)); 8285 contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", 8286 mBuilder.getPrimaryTextColor(p)); 8287 contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", 8288 mBuilder.getSecondaryTextColor(p)); 8289 contentView.setInt(R.id.status_bar_latest_event_content, 8290 "setNotificationBackgroundColor", 8291 mBuilder.getBackgroundColor(p)); 8292 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", 8293 isCollapsed); 8294 contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", 8295 mBuilder.mN.mLargeIcon); 8296 contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", 8297 nameReplacement); 8298 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", 8299 isOneToOne); 8300 contentView.setCharSequence(R.id.status_bar_latest_event_content, 8301 "setConversationTitle", conversationTitle); 8302 if (isConversationLayout) { 8303 contentView.setIcon(R.id.status_bar_latest_event_content, 8304 "setShortcutIcon", mShortcutIcon); 8305 contentView.setBoolean(R.id.status_bar_latest_event_content, 8306 "setIsImportantConversation", isImportantConversation); 8307 } 8308 if (isHeaderless) { 8309 // Collapsed legacy messaging style has a 1-line limit. 8310 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); 8311 } 8312 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 8313 largeIcon); 8314 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 8315 mBuilder.mN.extras); 8316 return contentView; 8317 } 8318 getKey(Person person)8319 private CharSequence getKey(Person person) { 8320 return person == null ? null 8321 : person.getKey() == null ? person.getName() : person.getKey(); 8322 } 8323 getOtherPersonName()8324 private CharSequence getOtherPersonName() { 8325 CharSequence userKey = getKey(mUser); 8326 for (int i = mMessages.size() - 1; i >= 0; i--) { 8327 Person sender = mMessages.get(i).getSenderPerson(); 8328 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) { 8329 return sender.getName(); 8330 } 8331 } 8332 return null; 8333 } 8334 hasOnlyWhiteSpaceSenders()8335 private boolean hasOnlyWhiteSpaceSenders() { 8336 for (int i = 0; i < mMessages.size(); i++) { 8337 Message m = mMessages.get(i); 8338 Person sender = m.getSenderPerson(); 8339 if (sender != null && !isWhiteSpace(sender.getName())) { 8340 return false; 8341 } 8342 } 8343 return true; 8344 } 8345 isWhiteSpace(CharSequence sender)8346 private boolean isWhiteSpace(CharSequence sender) { 8347 if (TextUtils.isEmpty(sender)) { 8348 return true; 8349 } 8350 if (sender.toString().matches("^\\s*$")) { 8351 return true; 8352 } 8353 // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround 8354 // For the presentation that we had. 8355 for (int i = 0; i < sender.length(); i++) { 8356 char c = sender.charAt(i); 8357 if (c != '\u200B') { 8358 return false; 8359 } 8360 } 8361 return true; 8362 } 8363 8364 /** 8365 * @hide 8366 */ 8367 @Override makeHeadsUpContentView(boolean increasedHeight)8368 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8369 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 8370 } 8371 8372 public static final class Message { 8373 /** @hide */ 8374 public static final String KEY_TEXT = "text"; 8375 static final String KEY_TIMESTAMP = "time"; 8376 static final String KEY_SENDER = "sender"; 8377 static final String KEY_SENDER_PERSON = "sender_person"; 8378 static final String KEY_DATA_MIME_TYPE = "type"; 8379 static final String KEY_DATA_URI= "uri"; 8380 static final String KEY_EXTRAS_BUNDLE = "extras"; 8381 static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history"; 8382 8383 private final CharSequence mText; 8384 private final long mTimestamp; 8385 @Nullable 8386 private final Person mSender; 8387 /** True if this message was generated from the extra 8388 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} 8389 */ 8390 private final boolean mRemoteInputHistory; 8391 8392 private Bundle mExtras = new Bundle(); 8393 private String mDataMimeType; 8394 private Uri mDataUri; 8395 8396 /** 8397 * Constructor 8398 * @param text A {@link CharSequence} to be displayed as the message content 8399 * @param timestamp Time at which the message arrived 8400 * @param sender A {@link CharSequence} to be used for displaying the name of the 8401 * sender. Should be <code>null</code> for messages by the current user, in which case 8402 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 8403 * Should be unique amongst all individuals in the conversation, and should be 8404 * consistent during re-posts of the notification. 8405 * 8406 * @deprecated use {@code Message(CharSequence, long, Person)} 8407 */ Message(CharSequence text, long timestamp, CharSequence sender)8408 public Message(CharSequence text, long timestamp, CharSequence sender){ 8409 this(text, timestamp, sender == null ? null 8410 : new Person.Builder().setName(sender).build()); 8411 } 8412 8413 /** 8414 * Constructor 8415 * @param text A {@link CharSequence} to be displayed as the message content 8416 * @param timestamp Time at which the message arrived 8417 * @param sender The {@link Person} who sent the message. 8418 * Should be <code>null</code> for messages by the current user, in which case 8419 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8420 * <p> 8421 * The person provided should contain an Icon, set with 8422 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8423 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8424 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8425 * to differentiate between the different users. 8426 * </p> 8427 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8428 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { 8429 this(text, timestamp, sender, false /* remoteHistory */); 8430 } 8431 8432 /** 8433 * Constructor 8434 * @param text A {@link CharSequence} to be displayed as the message content 8435 * @param timestamp Time at which the message arrived 8436 * @param sender The {@link Person} who sent the message. 8437 * Should be <code>null</code> for messages by the current user, in which case 8438 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8439 * @param remoteInputHistory True if the messages was generated from the extra 8440 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8441 * <p> 8442 * The person provided should contain an Icon, set with 8443 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8444 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8445 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8446 * to differentiate between the different users. 8447 * </p> 8448 * @hide 8449 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8450 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender, 8451 boolean remoteInputHistory) { 8452 mText = safeCharSequence(text); 8453 mTimestamp = timestamp; 8454 mSender = sender; 8455 mRemoteInputHistory = remoteInputHistory; 8456 } 8457 8458 /** 8459 * Sets a binary blob of data and an associated MIME type for a message. In the case 8460 * where the platform doesn't support the MIME type, the original text provided in the 8461 * constructor will be used. 8462 * @param dataMimeType The MIME type of the content. See 8463 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME 8464 * types on Android and Android Wear. 8465 * @param dataUri The uri containing the content whose type is given by the MIME type. 8466 * <p class="note"> 8467 * <ol> 8468 * <li>Notification Listeners including the System UI need permission to access the 8469 * data the Uri points to. The recommended ways to do this are:</li> 8470 * <li>Store the data in your own ContentProvider, making sure that other apps have 8471 * the correct permission to access your provider. The preferred mechanism for 8472 * providing access is to use per-URI permissions which are temporary and only 8473 * grant access to the receiving application. An easy way to create a 8474 * ContentProvider like this is to use the FileProvider helper class.</li> 8475 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 8476 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 8477 * also store non-media types (see MediaStore.Files for more info). Files can be 8478 * inserted into the MediaStore using scanFile() after which a content:// style 8479 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 8480 * Note that once added to the system MediaStore the content is accessible to any 8481 * app on the device.</li> 8482 * </ol> 8483 * @return this object for method chaining 8484 */ setData(String dataMimeType, Uri dataUri)8485 public Message setData(String dataMimeType, Uri dataUri) { 8486 mDataMimeType = dataMimeType; 8487 mDataUri = dataUri; 8488 return this; 8489 } 8490 8491 /** 8492 * Get the text to be used for this message, or the fallback text if a type and content 8493 * Uri have been set 8494 */ getText()8495 public CharSequence getText() { 8496 return mText; 8497 } 8498 8499 /** 8500 * Get the time at which this message arrived 8501 */ getTimestamp()8502 public long getTimestamp() { 8503 return mTimestamp; 8504 } 8505 8506 /** 8507 * Get the extras Bundle for this message. 8508 */ getExtras()8509 public Bundle getExtras() { 8510 return mExtras; 8511 } 8512 8513 /** 8514 * Get the text used to display the contact's name in the messaging experience 8515 * 8516 * @deprecated use {@link #getSenderPerson()} 8517 */ getSender()8518 public CharSequence getSender() { 8519 return mSender == null ? null : mSender.getName(); 8520 } 8521 8522 /** 8523 * Get the sender associated with this message. 8524 */ 8525 @Nullable getSenderPerson()8526 public Person getSenderPerson() { 8527 return mSender; 8528 } 8529 8530 /** 8531 * Get the MIME type of the data pointed to by the Uri 8532 */ getDataMimeType()8533 public String getDataMimeType() { 8534 return mDataMimeType; 8535 } 8536 8537 /** 8538 * Get the Uri pointing to the content of the message. Can be null, in which case 8539 * {@see #getText()} is used. 8540 */ getDataUri()8541 public Uri getDataUri() { 8542 return mDataUri; 8543 } 8544 8545 /** 8546 * @return True if the message was generated from 8547 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8548 * @hide 8549 */ isRemoteInputHistory()8550 public boolean isRemoteInputHistory() { 8551 return mRemoteInputHistory; 8552 } 8553 8554 /** 8555 * @hide 8556 */ 8557 @VisibleForTesting toBundle()8558 public Bundle toBundle() { 8559 Bundle bundle = new Bundle(); 8560 if (mText != null) { 8561 bundle.putCharSequence(KEY_TEXT, mText); 8562 } 8563 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 8564 if (mSender != null) { 8565 // Legacy listeners need this 8566 bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName())); 8567 bundle.putParcelable(KEY_SENDER_PERSON, mSender); 8568 } 8569 if (mDataMimeType != null) { 8570 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 8571 } 8572 if (mDataUri != null) { 8573 bundle.putParcelable(KEY_DATA_URI, mDataUri); 8574 } 8575 if (mExtras != null) { 8576 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 8577 } 8578 if (mRemoteInputHistory) { 8579 bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory); 8580 } 8581 return bundle; 8582 } 8583 getBundleArrayForMessages(List<Message> messages)8584 static Bundle[] getBundleArrayForMessages(List<Message> messages) { 8585 Bundle[] bundles = new Bundle[messages.size()]; 8586 final int N = messages.size(); 8587 for (int i = 0; i < N; i++) { 8588 bundles[i] = messages.get(i).toBundle(); 8589 } 8590 return bundles; 8591 } 8592 8593 /** 8594 * Returns a list of messages read from the given bundle list, e.g. 8595 * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}. 8596 */ 8597 @NonNull getMessagesFromBundleArray(@ullable Parcelable[] bundles)8598 public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) { 8599 if (bundles == null) { 8600 return new ArrayList<>(); 8601 } 8602 List<Message> messages = new ArrayList<>(bundles.length); 8603 for (int i = 0; i < bundles.length; i++) { 8604 if (bundles[i] instanceof Bundle) { 8605 Message message = getMessageFromBundle((Bundle)bundles[i]); 8606 if (message != null) { 8607 messages.add(message); 8608 } 8609 } 8610 } 8611 return messages; 8612 } 8613 8614 /** 8615 * Returns the message that is stored in the bundle (e.g. one of the values in the lists 8616 * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the 8617 * message couldn't be resolved. 8618 * @hide 8619 */ 8620 @Nullable getMessageFromBundle(@onNull Bundle bundle)8621 public static Message getMessageFromBundle(@NonNull Bundle bundle) { 8622 try { 8623 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 8624 return null; 8625 } else { 8626 8627 Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON); 8628 if (senderPerson == null) { 8629 // Legacy apps that use compat don't actually provide the sender objects 8630 // We need to fix the compat version to provide people / use 8631 // the native api instead 8632 CharSequence senderName = bundle.getCharSequence(KEY_SENDER); 8633 if (senderName != null) { 8634 senderPerson = new Person.Builder().setName(senderName).build(); 8635 } 8636 } 8637 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 8638 bundle.getLong(KEY_TIMESTAMP), 8639 senderPerson, 8640 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false)); 8641 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 8642 bundle.containsKey(KEY_DATA_URI)) { 8643 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 8644 (Uri) bundle.getParcelable(KEY_DATA_URI)); 8645 } 8646 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 8647 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 8648 } 8649 return message; 8650 } 8651 } catch (ClassCastException e) { 8652 return null; 8653 } 8654 } 8655 } 8656 } 8657 8658 /** 8659 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 8660 * 8661 * Here's how you'd set the <code>InboxStyle</code> on a notification: 8662 * <pre class="prettyprint"> 8663 * Notification notif = new Notification.Builder(mContext) 8664 * .setContentTitle("5 New mails from " + sender.toString()) 8665 * .setContentText(subject) 8666 * .setSmallIcon(R.drawable.new_mail) 8667 * .setLargeIcon(aBitmap) 8668 * .setStyle(new Notification.InboxStyle() 8669 * .addLine(str1) 8670 * .addLine(str2) 8671 * .setContentTitle("") 8672 * .setSummaryText("+3 more")) 8673 * .build(); 8674 * </pre> 8675 * 8676 * @see Notification#bigContentView 8677 */ 8678 public static class InboxStyle extends Style { 8679 8680 /** 8681 * The number of lines of remote input history allowed until we start reducing lines. 8682 */ 8683 private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1; 8684 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 8685 InboxStyle()8686 public InboxStyle() { 8687 } 8688 8689 /** 8690 * @deprecated use {@code InboxStyle()}. 8691 */ 8692 @Deprecated InboxStyle(Builder builder)8693 public InboxStyle(Builder builder) { 8694 setBuilder(builder); 8695 } 8696 8697 /** 8698 * Overrides ContentTitle in the big form of the template. 8699 * This defaults to the value passed to setContentTitle(). 8700 */ setBigContentTitle(CharSequence title)8701 public InboxStyle setBigContentTitle(CharSequence title) { 8702 internalSetBigContentTitle(safeCharSequence(title)); 8703 return this; 8704 } 8705 8706 /** 8707 * Set the first line of text after the detail section in the big form of the template. 8708 */ setSummaryText(CharSequence cs)8709 public InboxStyle setSummaryText(CharSequence cs) { 8710 internalSetSummaryText(safeCharSequence(cs)); 8711 return this; 8712 } 8713 8714 /** 8715 * Append a line to the digest section of the Inbox notification. 8716 */ addLine(CharSequence cs)8717 public InboxStyle addLine(CharSequence cs) { 8718 mTexts.add(safeCharSequence(cs)); 8719 return this; 8720 } 8721 8722 /** 8723 * @hide 8724 */ getLines()8725 public ArrayList<CharSequence> getLines() { 8726 return mTexts; 8727 } 8728 8729 /** 8730 * @hide 8731 */ addExtras(Bundle extras)8732 public void addExtras(Bundle extras) { 8733 super.addExtras(extras); 8734 8735 CharSequence[] a = new CharSequence[mTexts.size()]; 8736 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 8737 } 8738 8739 /** 8740 * @hide 8741 */ 8742 @Override restoreFromExtras(Bundle extras)8743 protected void restoreFromExtras(Bundle extras) { 8744 super.restoreFromExtras(extras); 8745 8746 mTexts.clear(); 8747 if (extras.containsKey(EXTRA_TEXT_LINES)) { 8748 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 8749 } 8750 } 8751 8752 /** 8753 * @hide 8754 */ makeBigContentView()8755 public RemoteViews makeBigContentView() { 8756 StandardTemplateParams p = mBuilder.mParams.reset() 8757 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 8758 .fillTextsFrom(mBuilder).text(null); 8759 TemplateBindResult result = new TemplateBindResult(); 8760 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result); 8761 8762 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 8763 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 8764 8765 // Make sure all rows are gone in case we reuse a view. 8766 for (int rowId : rowIds) { 8767 contentView.setViewVisibility(rowId, View.GONE); 8768 } 8769 8770 int i=0; 8771 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8772 R.dimen.notification_inbox_item_top_padding); 8773 boolean first = true; 8774 int onlyViewId = 0; 8775 int maxRows = rowIds.length; 8776 if (mBuilder.mActions.size() > 0) { 8777 maxRows--; 8778 } 8779 RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( 8780 mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 8781 RemoteInputHistoryItem.class); 8782 if (remoteInputHistory != null 8783 && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { 8784 // Let's remove some messages to make room for the remote input history. 8785 // 1 is always able to fit, but let's remove them if they are 2 or 3 8786 int numRemoteInputs = Math.min(remoteInputHistory.length, 8787 MAX_REMOTE_INPUT_HISTORY_LINES); 8788 int totalNumRows = mTexts.size() + numRemoteInputs 8789 - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION; 8790 if (totalNumRows > maxRows) { 8791 int overflow = totalNumRows - maxRows; 8792 if (mTexts.size() > maxRows) { 8793 // Heuristic: if the Texts don't fit anyway, we'll rather drop the last 8794 // few messages, even with the remote input 8795 maxRows -= overflow; 8796 } else { 8797 // otherwise we drop the first messages 8798 i = overflow; 8799 } 8800 } 8801 } 8802 while (i < mTexts.size() && i < maxRows) { 8803 CharSequence str = mTexts.get(i); 8804 if (!TextUtils.isEmpty(str)) { 8805 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 8806 contentView.setTextViewText(rowIds[i], 8807 mBuilder.processTextSpans(mBuilder.processLegacyText(str))); 8808 mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); 8809 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 8810 if (first) { 8811 onlyViewId = rowIds[i]; 8812 } else { 8813 onlyViewId = 0; 8814 } 8815 first = false; 8816 } 8817 i++; 8818 } 8819 if (onlyViewId != 0) { 8820 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 8821 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8822 R.dimen.notification_text_margin_top); 8823 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 8824 } 8825 8826 return contentView; 8827 } 8828 8829 /** 8830 * @hide 8831 */ 8832 @Override areNotificationsVisiblyDifferent(Style other)8833 public boolean areNotificationsVisiblyDifferent(Style other) { 8834 if (other == null || getClass() != other.getClass()) { 8835 return true; 8836 } 8837 InboxStyle newS = (InboxStyle) other; 8838 8839 final ArrayList<CharSequence> myLines = getLines(); 8840 final ArrayList<CharSequence> newLines = newS.getLines(); 8841 final int n = myLines.size(); 8842 if (n != newLines.size()) { 8843 return true; 8844 } 8845 8846 for (int i = 0; i < n; i++) { 8847 if (!Objects.equals( 8848 String.valueOf(myLines.get(i)), 8849 String.valueOf(newLines.get(i)))) { 8850 return true; 8851 } 8852 } 8853 return false; 8854 } 8855 } 8856 8857 /** 8858 * Notification style for media playback notifications. 8859 * 8860 * In the expanded form, {@link Notification#bigContentView}, up to 5 8861 * {@link Notification.Action}s specified with 8862 * {@link Notification.Builder#addAction(Action) addAction} will be 8863 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 8864 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 8865 * treated as album artwork. 8866 * <p> 8867 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 8868 * {@link Notification#contentView}; by providing action indices to 8869 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 8870 * in the standard view alongside the usual content. 8871 * <p> 8872 * Notifications created with MediaStyle will have their category set to 8873 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 8874 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 8875 * <p> 8876 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 8877 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 8878 * the System UI can identify this as a notification representing an active media session 8879 * and respond accordingly (by showing album artwork in the lockscreen, for example). 8880 * 8881 * <p> 8882 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a 8883 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized. 8884 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}. 8885 * <p> 8886 * 8887 * To use this style with your Notification, feed it to 8888 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 8889 * <pre class="prettyprint"> 8890 * Notification noti = new Notification.Builder() 8891 * .setSmallIcon(R.drawable.ic_stat_player) 8892 * .setContentTitle("Track title") 8893 * .setContentText("Artist - Album") 8894 * .setLargeIcon(albumArtBitmap)) 8895 * .setStyle(<b>new Notification.MediaStyle()</b> 8896 * .setMediaSession(mySession)) 8897 * .build(); 8898 * </pre> 8899 * 8900 * @see Notification#bigContentView 8901 * @see Notification.Builder#setColorized(boolean) 8902 */ 8903 public static class MediaStyle extends Style { 8904 // Changing max media buttons requires also changing templates 8905 // (notification_template_material_media and notification_template_material_big_media). 8906 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 8907 static final int MAX_MEDIA_BUTTONS = 5; 8908 @IdRes private static final int[] MEDIA_BUTTON_IDS = { 8909 R.id.action0, 8910 R.id.action1, 8911 R.id.action2, 8912 R.id.action3, 8913 R.id.action4, 8914 }; 8915 8916 private int[] mActionsToShowInCompact = null; 8917 private MediaSession.Token mToken; 8918 MediaStyle()8919 public MediaStyle() { 8920 } 8921 8922 /** 8923 * @deprecated use {@code MediaStyle()}. 8924 */ 8925 @Deprecated MediaStyle(Builder builder)8926 public MediaStyle(Builder builder) { 8927 setBuilder(builder); 8928 } 8929 8930 /** 8931 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 8932 * notification view. 8933 * 8934 * @param actions the indices of the actions to show in the compact notification view 8935 */ setShowActionsInCompactView(int...actions)8936 public MediaStyle setShowActionsInCompactView(int...actions) { 8937 mActionsToShowInCompact = actions; 8938 return this; 8939 } 8940 8941 /** 8942 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 8943 * to provide additional playback information and control to the SystemUI. 8944 */ setMediaSession(MediaSession.Token token)8945 public MediaStyle setMediaSession(MediaSession.Token token) { 8946 mToken = token; 8947 return this; 8948 } 8949 8950 /** 8951 * @hide 8952 */ 8953 @Override 8954 @UnsupportedAppUsage buildStyled(Notification wip)8955 public Notification buildStyled(Notification wip) { 8956 super.buildStyled(wip); 8957 if (wip.category == null) { 8958 wip.category = Notification.CATEGORY_TRANSPORT; 8959 } 8960 return wip; 8961 } 8962 8963 /** 8964 * @hide 8965 */ 8966 @Override makeContentView(boolean increasedHeight)8967 public RemoteViews makeContentView(boolean increasedHeight) { 8968 return makeMediaContentView(null /* customContent */); 8969 } 8970 8971 /** 8972 * @hide 8973 */ 8974 @Override makeBigContentView()8975 public RemoteViews makeBigContentView() { 8976 return makeMediaBigContentView(null /* customContent */); 8977 } 8978 8979 /** 8980 * @hide 8981 */ 8982 @Override makeHeadsUpContentView(boolean increasedHeight)8983 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8984 return makeMediaContentView(null /* customContent */); 8985 } 8986 8987 /** @hide */ 8988 @Override addExtras(Bundle extras)8989 public void addExtras(Bundle extras) { 8990 super.addExtras(extras); 8991 8992 if (mToken != null) { 8993 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 8994 } 8995 if (mActionsToShowInCompact != null) { 8996 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 8997 } 8998 } 8999 9000 /** 9001 * @hide 9002 */ 9003 @Override restoreFromExtras(Bundle extras)9004 protected void restoreFromExtras(Bundle extras) { 9005 super.restoreFromExtras(extras); 9006 9007 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 9008 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION); 9009 } 9010 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 9011 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 9012 } 9013 } 9014 9015 /** 9016 * @hide 9017 */ 9018 @Override areNotificationsVisiblyDifferent(Style other)9019 public boolean areNotificationsVisiblyDifferent(Style other) { 9020 if (other == null || getClass() != other.getClass()) { 9021 return true; 9022 } 9023 // All fields to compare are on the Notification object 9024 return false; 9025 } 9026 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)9027 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 9028 Action action, StandardTemplateParams p) { 9029 final boolean tombstone = (action.actionIntent == null); 9030 container.setViewVisibility(buttonId, View.VISIBLE); 9031 container.setImageViewIcon(buttonId, action.getIcon()); 9032 9033 // If the action buttons should not be tinted, then just use the default 9034 // notification color. Otherwise, just use the passed-in color. 9035 int tintColor = mBuilder.getStandardActionColor(p); 9036 9037 container.setDrawableTint(buttonId, false, tintColor, 9038 PorterDuff.Mode.SRC_ATOP); 9039 9040 int rippleAlpha = mBuilder.getColors(p).getRippleAlpha(); 9041 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 9042 Color.blue(tintColor)); 9043 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 9044 9045 if (!tombstone) { 9046 container.setOnClickPendingIntent(buttonId, action.actionIntent); 9047 } 9048 container.setContentDescription(buttonId, action.title); 9049 } 9050 9051 /** @hide */ makeMediaContentView(@ullable RemoteViews customContent)9052 protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) { 9053 final int numActions = mBuilder.mActions.size(); 9054 final int numActionsToShow = Math.min(mActionsToShowInCompact == null 9055 ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 9056 if (numActionsToShow > numActions) { 9057 throw new IllegalArgumentException(String.format( 9058 "setShowActionsInCompactView: action %d out of bounds (max %d)", 9059 numActions, numActions - 1)); 9060 } 9061 9062 StandardTemplateParams p = mBuilder.mParams.reset() 9063 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 9064 .hideTime(numActionsToShow > 1) // hide if actions wider than a right icon 9065 .hideSubText(numActionsToShow > 1) // hide if actions wider than a right icon 9066 .hideLeftIcon(false) // allow large icon on left when grouped 9067 .hideRightIcon(numActionsToShow > 0) // right icon or actions; not both 9068 .hideProgress(true) 9069 .fillTextsFrom(mBuilder); 9070 TemplateBindResult result = new TemplateBindResult(); 9071 RemoteViews template = mBuilder.applyStandardTemplate( 9072 R.layout.notification_template_material_media, p, 9073 null /* result */); 9074 9075 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 9076 if (i < numActionsToShow) { 9077 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 9078 bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p); 9079 } else { 9080 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9081 } 9082 } 9083 // Prevent a swooping expand animation when there are no actions 9084 boolean hasActions = numActionsToShow != 0; 9085 template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE); 9086 9087 // Add custom view if provided by subclass. 9088 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9089 return template; 9090 } 9091 9092 /** @hide */ makeMediaBigContentView(@ullable RemoteViews customContent)9093 protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) { 9094 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 9095 StandardTemplateParams p = mBuilder.mParams.reset() 9096 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9097 .hideProgress(true) 9098 .fillTextsFrom(mBuilder); 9099 TemplateBindResult result = new TemplateBindResult(); 9100 RemoteViews template = mBuilder.applyStandardTemplate( 9101 R.layout.notification_template_material_big_media, p , result); 9102 9103 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 9104 if (i < actionCount) { 9105 bindMediaActionButton(template, 9106 MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 9107 } else { 9108 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9109 } 9110 } 9111 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9112 return template; 9113 } 9114 } 9115 9116 /** 9117 * Helper class for generating large-format notifications that include a large image attachment. 9118 * 9119 * Here's how you'd set the <code>CallStyle</code> on a notification: 9120 * <pre class="prettyprint"> 9121 * Notification notif = new Notification.Builder(mContext) 9122 * .setSmallIcon(R.drawable.new_post) 9123 * .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent)) 9124 * .build(); 9125 * </pre> 9126 */ 9127 public static class CallStyle extends Style { 9128 /** @hide */ 9129 public static final int CALL_TYPE_INCOMING = 1; 9130 /** @hide */ 9131 public static final int CALL_TYPE_ONGOING = 2; 9132 /** @hide */ 9133 public static final int CALL_TYPE_SCREENING = 3; 9134 9135 /** 9136 * This is a key used privately on the action.extras to give spacing priority 9137 * to the required call actions 9138 */ 9139 private static final String KEY_ACTION_PRIORITY = "key_action_priority"; 9140 9141 private int mCallType; 9142 private Person mPerson; 9143 private PendingIntent mAnswerIntent; 9144 private PendingIntent mDeclineIntent; 9145 private PendingIntent mHangUpIntent; 9146 private boolean mIsVideo; 9147 private Integer mAnswerButtonColor; 9148 private Integer mDeclineButtonColor; 9149 private Icon mVerificationIcon; 9150 private CharSequence mVerificationText; 9151 CallStyle()9152 CallStyle() { 9153 } 9154 9155 /** 9156 * Create a CallStyle for an incoming call. 9157 * This notification will have a decline and an answer action, will allow a single 9158 * custom {@link Builder#addAction(Action) action}, and will have a default 9159 * {@link Builder#setContentText(CharSequence) content text} for an incoming call. 9160 * 9161 * @param person The person displayed as the caller. 9162 * The person also needs to have a non-empty name associated with it. 9163 * @param declineIntent The intent to be sent when the user taps the decline action 9164 * @param answerIntent The intent to be sent when the user taps the answer action 9165 */ 9166 @NonNull forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9167 public static CallStyle forIncomingCall(@NonNull Person person, 9168 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) { 9169 return new CallStyle(CALL_TYPE_INCOMING, person, 9170 null /* hangUpIntent */, 9171 requireNonNull(declineIntent, "declineIntent is required"), 9172 requireNonNull(answerIntent, "answerIntent is required") 9173 ); 9174 } 9175 9176 /** 9177 * Create a CallStyle for an ongoing call. 9178 * This notification will have a hang up action, will allow up to two 9179 * custom {@link Builder#addAction(Action) actions}, and will have a default 9180 * {@link Builder#setContentText(CharSequence) content text} for an ongoing call. 9181 * 9182 * @param person The person displayed as being on the other end of the call. 9183 * The person also needs to have a non-empty name associated with it. 9184 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9185 */ 9186 @NonNull forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9187 public static CallStyle forOngoingCall(@NonNull Person person, 9188 @NonNull PendingIntent hangUpIntent) { 9189 return new CallStyle(CALL_TYPE_ONGOING, person, 9190 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9191 null /* declineIntent */, 9192 null /* answerIntent */ 9193 ); 9194 } 9195 9196 /** 9197 * Create a CallStyle for a call that is being screened. 9198 * This notification will have a hang up and an answer action, will allow a single 9199 * custom {@link Builder#addAction(Action) action}, and will have a default 9200 * {@link Builder#setContentText(CharSequence) content text} for a call that is being 9201 * screened. 9202 * 9203 * @param person The person displayed as the caller. 9204 * The person also needs to have a non-empty name associated with it. 9205 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9206 * @param answerIntent The intent to be sent when the user taps the answer action 9207 */ 9208 @NonNull forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9209 public static CallStyle forScreeningCall(@NonNull Person person, 9210 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) { 9211 return new CallStyle(CALL_TYPE_SCREENING, person, 9212 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9213 null /* declineIntent */, 9214 requireNonNull(answerIntent, "answerIntent is required") 9215 ); 9216 } 9217 9218 /** 9219 * @param person The person displayed for the incoming call. 9220 * The user also needs to have a non-empty name associated with it. 9221 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9222 * @param declineIntent The intent to be sent when the user taps the decline action 9223 * @param answerIntent The intent to be sent when the user taps the answer action 9224 */ CallStyle(int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9225 private CallStyle(int callType, @NonNull Person person, 9226 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, 9227 @Nullable PendingIntent answerIntent) { 9228 if (person == null || TextUtils.isEmpty(person.getName())) { 9229 throw new IllegalArgumentException("person must have a non-empty a name"); 9230 } 9231 mCallType = callType; 9232 mPerson = person; 9233 mAnswerIntent = answerIntent; 9234 mDeclineIntent = declineIntent; 9235 mHangUpIntent = hangUpIntent; 9236 } 9237 9238 /** 9239 * Sets whether the call is a video call, which may affect the icons or text used on the 9240 * required action buttons. 9241 */ 9242 @NonNull setIsVideo(boolean isVideo)9243 public CallStyle setIsVideo(boolean isVideo) { 9244 mIsVideo = isVideo; 9245 return this; 9246 } 9247 9248 /** 9249 * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text} 9250 * as a verification status of the caller. 9251 */ 9252 @NonNull setVerificationIcon(@ullable Icon verificationIcon)9253 public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) { 9254 mVerificationIcon = verificationIcon; 9255 return this; 9256 } 9257 9258 /** 9259 * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon} 9260 * as a verification status of the caller. 9261 */ 9262 @NonNull setVerificationText(@ullable CharSequence verificationText)9263 public CallStyle setVerificationText(@Nullable CharSequence verificationText) { 9264 mVerificationText = safeCharSequence(verificationText); 9265 return this; 9266 } 9267 9268 /** 9269 * Optional color to be used as a hint for the Answer action button's color. 9270 * The system may change this color to ensure sufficient contrast with the background. 9271 * The system may choose to disregard this hint if the notification is not colorized. 9272 */ 9273 @NonNull setAnswerButtonColorHint(@olorInt int color)9274 public CallStyle setAnswerButtonColorHint(@ColorInt int color) { 9275 mAnswerButtonColor = color; 9276 return this; 9277 } 9278 9279 /** 9280 * Optional color to be used as a hint for the Decline or Hang Up action button's color. 9281 * The system may change this color to ensure sufficient contrast with the background. 9282 * The system may choose to disregard this hint if the notification is not colorized. 9283 */ 9284 @NonNull setDeclineButtonColorHint(@olorInt int color)9285 public CallStyle setDeclineButtonColorHint(@ColorInt int color) { 9286 mDeclineButtonColor = color; 9287 return this; 9288 } 9289 9290 /** @hide */ 9291 @Override buildStyled(Notification wip)9292 public Notification buildStyled(Notification wip) { 9293 wip = super.buildStyled(wip); 9294 // ensure that the actions in the builder and notification are corrected. 9295 mBuilder.mActions = getActionsListWithSystemActions(); 9296 wip.actions = new Action[mBuilder.mActions.size()]; 9297 mBuilder.mActions.toArray(wip.actions); 9298 return wip; 9299 } 9300 9301 /** 9302 * @hide 9303 */ displayCustomViewInline()9304 public boolean displayCustomViewInline() { 9305 // This is a lie; True is returned to make sure that the custom view is not used 9306 // instead of the template, but it will not actually be included. 9307 return true; 9308 } 9309 9310 /** 9311 * @hide 9312 */ 9313 @Override purgeResources()9314 public void purgeResources() { 9315 super.purgeResources(); 9316 if (mVerificationIcon != null) { 9317 mVerificationIcon.convertToAshmem(); 9318 } 9319 } 9320 9321 /** 9322 * @hide 9323 */ 9324 @Override reduceImageSizes(Context context)9325 public void reduceImageSizes(Context context) { 9326 super.reduceImageSizes(context); 9327 if (mVerificationIcon != null) { 9328 int rightIconSize = context.getResources().getDimensionPixelSize( 9329 ActivityManager.isLowRamDeviceStatic() 9330 ? R.dimen.notification_right_icon_size_low_ram 9331 : R.dimen.notification_right_icon_size); 9332 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 9333 } 9334 } 9335 9336 /** 9337 * @hide 9338 */ 9339 @Override makeContentView(boolean increasedHeight)9340 public RemoteViews makeContentView(boolean increasedHeight) { 9341 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL); 9342 } 9343 9344 /** 9345 * @hide 9346 */ 9347 @Override makeHeadsUpContentView(boolean increasedHeight)9348 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9349 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 9350 } 9351 9352 /** 9353 * @hide 9354 */ makeBigContentView()9355 public RemoteViews makeBigContentView() { 9356 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG); 9357 } 9358 9359 @NonNull makeNegativeAction()9360 private Action makeNegativeAction() { 9361 if (mDeclineIntent == null) { 9362 return makeAction(R.drawable.ic_call_decline, 9363 R.string.call_notification_hang_up_action, 9364 mDeclineButtonColor, R.color.call_notification_decline_color, 9365 mHangUpIntent); 9366 } else { 9367 return makeAction(R.drawable.ic_call_decline, 9368 R.string.call_notification_decline_action, 9369 mDeclineButtonColor, R.color.call_notification_decline_color, 9370 mDeclineIntent); 9371 } 9372 } 9373 9374 @Nullable makeAnswerAction()9375 private Action makeAnswerAction() { 9376 return mAnswerIntent == null ? null : makeAction( 9377 mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer, 9378 mIsVideo ? R.string.call_notification_answer_video_action 9379 : R.string.call_notification_answer_action, 9380 mAnswerButtonColor, R.color.call_notification_answer_color, 9381 mAnswerIntent); 9382 } 9383 9384 @NonNull makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9385 private Action makeAction(@DrawableRes int icon, @StringRes int title, 9386 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) { 9387 if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) { 9388 colorInt = mBuilder.mContext.getColor(defaultColorRes); 9389 } 9390 Action action = new Action.Builder(Icon.createWithResource("", icon), 9391 new SpannableStringBuilder().append(mBuilder.mContext.getString(title), 9392 new ForegroundColorSpan(colorInt), 9393 SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE), 9394 intent).build(); 9395 action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true); 9396 return action; 9397 } 9398 isActionAddedByCallStyle(Action action)9399 private boolean isActionAddedByCallStyle(Action action) { 9400 // This is an internal extra added by the style to these actions. If an app were to add 9401 // this extra to the action themselves, the action would be dropped. :shrug: 9402 return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY); 9403 } 9404 9405 /** 9406 * Gets the actions list for the call with the answer/decline/hangUp actions inserted in 9407 * the correct place. This returns the correct result even if the system actions have 9408 * already been added, and even if more actions were added since then. 9409 * @hide 9410 */ 9411 @NonNull getActionsListWithSystemActions()9412 public ArrayList<Action> getActionsListWithSystemActions() { 9413 // Define the system actions we expect to see 9414 final Action negativeAction = makeNegativeAction(); 9415 final Action answerAction = makeAnswerAction(); 9416 // Sort the expected actions into the correct order: 9417 // * If there's no answer action, put the hang up / decline action at the end 9418 // * Otherwise put the answer action at the end, and put the decline action at start. 9419 final Action firstAction = answerAction == null ? null : negativeAction; 9420 final Action lastAction = answerAction == null ? negativeAction : answerAction; 9421 9422 // Start creating the result list. 9423 int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS; 9424 ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS); 9425 if (firstAction != null) { 9426 resultActions.add(firstAction); 9427 --nonContextualActionSlotsRemaining; 9428 } 9429 9430 // Copy actions into the new list, correcting system actions. 9431 if (mBuilder.mActions != null) { 9432 for (Notification.Action action : mBuilder.mActions) { 9433 if (action.isContextual()) { 9434 // Always include all contextual actions 9435 resultActions.add(action); 9436 } else if (isActionAddedByCallStyle(action)) { 9437 // Drop any old versions of system actions 9438 } else { 9439 // Copy non-contextual actions; decrement the remaining action slots. 9440 resultActions.add(action); 9441 --nonContextualActionSlotsRemaining; 9442 } 9443 // If there's exactly one action slot left, fill it with the lastAction. 9444 if (nonContextualActionSlotsRemaining == 1) { 9445 resultActions.add(lastAction); 9446 --nonContextualActionSlotsRemaining; 9447 } 9448 } 9449 } 9450 // If there are any action slots left, the lastAction still needs to be added. 9451 if (nonContextualActionSlotsRemaining >= 1) { 9452 resultActions.add(lastAction); 9453 } 9454 return resultActions; 9455 } 9456 makeCallLayout(int viewType)9457 private RemoteViews makeCallLayout(int viewType) { 9458 final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL; 9459 Bundle extras = mBuilder.mN.extras; 9460 CharSequence title = mPerson != null ? mPerson.getName() : null; 9461 CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 9462 if (text == null) { 9463 text = getDefaultText(); 9464 } 9465 9466 // Bind standard template 9467 StandardTemplateParams p = mBuilder.mParams.reset() 9468 .viewType(viewType) 9469 .callStyleActions(true) 9470 .allowTextWithProgress(true) 9471 .hideLeftIcon(true) 9472 .hideRightIcon(true) 9473 .hideAppName(isCollapsed) 9474 .titleViewId(R.id.conversation_text) 9475 .title(title) 9476 .text(text) 9477 .summaryText(mBuilder.processLegacyText(mVerificationText)); 9478 mBuilder.mActions = getActionsListWithSystemActions(); 9479 final RemoteViews contentView; 9480 if (isCollapsed) { 9481 contentView = mBuilder.applyStandardTemplate( 9482 R.layout.notification_template_material_call, p, null /* result */); 9483 } else { 9484 contentView = mBuilder.applyStandardTemplateWithActions( 9485 R.layout.notification_template_material_big_call, p, null /* result */); 9486 } 9487 9488 // Bind some extra conversation-specific header fields. 9489 if (!p.mHideAppName) { 9490 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 9491 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE); 9492 } 9493 bindCallerVerification(contentView, p); 9494 9495 // Bind some custom CallLayout properties 9496 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 9497 mBuilder.getSmallIconColor(p)); 9498 contentView.setInt(R.id.status_bar_latest_event_content, 9499 "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); 9500 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 9501 mBuilder.mN.mLargeIcon); 9502 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 9503 mBuilder.mN.extras); 9504 9505 return contentView; 9506 } 9507 bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9508 private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) { 9509 String iconContentDescription = null; 9510 boolean showDivider = true; 9511 if (mVerificationIcon != null) { 9512 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon); 9513 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */, 9514 mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 9515 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE); 9516 iconContentDescription = mBuilder.mContext.getString( 9517 R.string.notification_verified_content_description); 9518 showDivider = false; // the icon replaces the divider 9519 } else { 9520 contentView.setViewVisibility(R.id.verification_icon, View.GONE); 9521 } 9522 if (!TextUtils.isEmpty(mVerificationText)) { 9523 contentView.setTextViewText(R.id.verification_text, mVerificationText); 9524 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p); 9525 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE); 9526 iconContentDescription = null; // let the app's text take precedence 9527 } else { 9528 contentView.setViewVisibility(R.id.verification_text, View.GONE); 9529 showDivider = false; // no divider if no text 9530 } 9531 contentView.setContentDescription(R.id.verification_icon, iconContentDescription); 9532 if (showDivider) { 9533 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE); 9534 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p); 9535 } else { 9536 contentView.setViewVisibility(R.id.verification_divider, View.GONE); 9537 } 9538 } 9539 9540 @Nullable getDefaultText()9541 private String getDefaultText() { 9542 switch (mCallType) { 9543 case CALL_TYPE_INCOMING: 9544 return mBuilder.mContext.getString(R.string.call_notification_incoming_text); 9545 case CALL_TYPE_ONGOING: 9546 return mBuilder.mContext.getString(R.string.call_notification_ongoing_text); 9547 case CALL_TYPE_SCREENING: 9548 return mBuilder.mContext.getString(R.string.call_notification_screening_text); 9549 } 9550 return null; 9551 } 9552 9553 /** 9554 * @hide 9555 */ addExtras(Bundle extras)9556 public void addExtras(Bundle extras) { 9557 super.addExtras(extras); 9558 extras.putInt(EXTRA_CALL_TYPE, mCallType); 9559 extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo); 9560 extras.putParcelable(EXTRA_CALL_PERSON, mPerson); 9561 if (mVerificationIcon != null) { 9562 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon); 9563 } 9564 if (mVerificationText != null) { 9565 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText); 9566 } 9567 if (mAnswerIntent != null) { 9568 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent); 9569 } 9570 if (mDeclineIntent != null) { 9571 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent); 9572 } 9573 if (mHangUpIntent != null) { 9574 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent); 9575 } 9576 if (mAnswerButtonColor != null) { 9577 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor); 9578 } 9579 if (mDeclineButtonColor != null) { 9580 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor); 9581 } 9582 fixTitleAndTextExtras(extras); 9583 } 9584 fixTitleAndTextExtras(Bundle extras)9585 private void fixTitleAndTextExtras(Bundle extras) { 9586 CharSequence sender = mPerson != null ? mPerson.getName() : null; 9587 if (sender != null) { 9588 extras.putCharSequence(EXTRA_TITLE, sender); 9589 } 9590 if (extras.getCharSequence(EXTRA_TEXT) == null) { 9591 extras.putCharSequence(EXTRA_TEXT, getDefaultText()); 9592 } 9593 } 9594 9595 /** 9596 * @hide 9597 */ 9598 @Override restoreFromExtras(Bundle extras)9599 protected void restoreFromExtras(Bundle extras) { 9600 super.restoreFromExtras(extras); 9601 mCallType = extras.getInt(EXTRA_CALL_TYPE); 9602 mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO); 9603 mPerson = extras.getParcelable(EXTRA_CALL_PERSON); 9604 mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON); 9605 mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT); 9606 mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT); 9607 mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT); 9608 mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT); 9609 mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR) 9610 ? extras.getInt(EXTRA_ANSWER_COLOR) : null; 9611 mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR) 9612 ? extras.getInt(EXTRA_DECLINE_COLOR) : null; 9613 } 9614 9615 /** 9616 * @hide 9617 */ 9618 @Override hasSummaryInHeader()9619 public boolean hasSummaryInHeader() { 9620 return false; 9621 } 9622 9623 /** 9624 * @hide 9625 */ 9626 @Override areNotificationsVisiblyDifferent(Style other)9627 public boolean areNotificationsVisiblyDifferent(Style other) { 9628 if (other == null || getClass() != other.getClass()) { 9629 return true; 9630 } 9631 CallStyle otherS = (CallStyle) other; 9632 return !Objects.equals(mCallType, otherS.mCallType) 9633 || !Objects.equals(mPerson, otherS.mPerson) 9634 || !Objects.equals(mVerificationText, otherS.mVerificationText); 9635 } 9636 } 9637 9638 /** 9639 * Notification style for custom views that are decorated by the system 9640 * 9641 * <p>Instead of providing a notification that is completely custom, a developer can set this 9642 * style and still obtain system decorations like the notification header with the expand 9643 * affordance and actions. 9644 * 9645 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 9646 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 9647 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 9648 * corresponding custom views to display. 9649 * 9650 * To use this style with your Notification, feed it to 9651 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9652 * <pre class="prettyprint"> 9653 * Notification noti = new Notification.Builder() 9654 * .setSmallIcon(R.drawable.ic_stat_player) 9655 * .setLargeIcon(albumArtBitmap)) 9656 * .setCustomContentView(contentView); 9657 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 9658 * .build(); 9659 * </pre> 9660 */ 9661 public static class DecoratedCustomViewStyle extends Style { 9662 DecoratedCustomViewStyle()9663 public DecoratedCustomViewStyle() { 9664 } 9665 9666 /** 9667 * @hide 9668 */ displayCustomViewInline()9669 public boolean displayCustomViewInline() { 9670 return true; 9671 } 9672 9673 /** 9674 * @hide 9675 */ 9676 @Override makeContentView(boolean increasedHeight)9677 public RemoteViews makeContentView(boolean increasedHeight) { 9678 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 9679 } 9680 9681 /** 9682 * @hide 9683 */ 9684 @Override makeBigContentView()9685 public RemoteViews makeBigContentView() { 9686 return makeDecoratedBigContentView(); 9687 } 9688 9689 /** 9690 * @hide 9691 */ 9692 @Override makeHeadsUpContentView(boolean increasedHeight)9693 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9694 return makeDecoratedHeadsUpContentView(); 9695 } 9696 makeDecoratedHeadsUpContentView()9697 private RemoteViews makeDecoratedHeadsUpContentView() { 9698 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 9699 ? mBuilder.mN.contentView 9700 : mBuilder.mN.headsUpContentView; 9701 if (headsUpContentView == null) { 9702 return null; // no custom view; use the default behavior 9703 } 9704 if (mBuilder.mActions.size() == 0) { 9705 return makeStandardTemplateWithCustomContent(headsUpContentView); 9706 } 9707 TemplateBindResult result = new TemplateBindResult(); 9708 StandardTemplateParams p = mBuilder.mParams.reset() 9709 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 9710 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9711 .fillTextsFrom(mBuilder); 9712 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 9713 mBuilder.getHeadsUpBaseLayoutResource(), p, result); 9714 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView, 9715 p, result); 9716 return remoteViews; 9717 } 9718 makeStandardTemplateWithCustomContent(RemoteViews customContent)9719 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 9720 if (customContent == null) { 9721 return null; // no custom view; use the default behavior 9722 } 9723 TemplateBindResult result = new TemplateBindResult(); 9724 StandardTemplateParams p = mBuilder.mParams.reset() 9725 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 9726 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9727 .fillTextsFrom(mBuilder); 9728 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 9729 mBuilder.getBaseLayoutResource(), p, result); 9730 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent, 9731 p, result); 9732 return remoteViews; 9733 } 9734 makeDecoratedBigContentView()9735 private RemoteViews makeDecoratedBigContentView() { 9736 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 9737 ? mBuilder.mN.contentView 9738 : mBuilder.mN.bigContentView; 9739 if (bigContentView == null) { 9740 return null; // no custom view; use the default behavior 9741 } 9742 TemplateBindResult result = new TemplateBindResult(); 9743 StandardTemplateParams p = mBuilder.mParams.reset() 9744 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9745 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9746 .fillTextsFrom(mBuilder); 9747 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 9748 mBuilder.getBigBaseLayoutResource(), p, result); 9749 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView, 9750 p, result); 9751 return remoteViews; 9752 } 9753 9754 /** 9755 * @hide 9756 */ 9757 @Override areNotificationsVisiblyDifferent(Style other)9758 public boolean areNotificationsVisiblyDifferent(Style other) { 9759 if (other == null || getClass() != other.getClass()) { 9760 return true; 9761 } 9762 // Comparison done for all custom RemoteViews, independent of style 9763 return false; 9764 } 9765 } 9766 9767 /** 9768 * Notification style for media custom views that are decorated by the system 9769 * 9770 * <p>Instead of providing a media notification that is completely custom, a developer can set 9771 * this style and still obtain system decorations like the notification header with the expand 9772 * affordance and actions. 9773 * 9774 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 9775 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 9776 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 9777 * corresponding custom views to display. 9778 * <p> 9779 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 9780 * notification by using {@link Notification.Builder#setColorized(boolean)}. 9781 * <p> 9782 * To use this style with your Notification, feed it to 9783 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9784 * <pre class="prettyprint"> 9785 * Notification noti = new Notification.Builder() 9786 * .setSmallIcon(R.drawable.ic_stat_player) 9787 * .setLargeIcon(albumArtBitmap)) 9788 * .setCustomContentView(contentView); 9789 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 9790 * .setMediaSession(mySession)) 9791 * .build(); 9792 * </pre> 9793 * 9794 * @see android.app.Notification.DecoratedCustomViewStyle 9795 * @see android.app.Notification.MediaStyle 9796 */ 9797 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 9798 DecoratedMediaCustomViewStyle()9799 public DecoratedMediaCustomViewStyle() { 9800 } 9801 9802 /** 9803 * @hide 9804 */ displayCustomViewInline()9805 public boolean displayCustomViewInline() { 9806 return true; 9807 } 9808 9809 /** 9810 * @hide 9811 */ 9812 @Override makeContentView(boolean increasedHeight)9813 public RemoteViews makeContentView(boolean increasedHeight) { 9814 return makeMediaContentView(mBuilder.mN.contentView); 9815 } 9816 9817 /** 9818 * @hide 9819 */ 9820 @Override makeBigContentView()9821 public RemoteViews makeBigContentView() { 9822 RemoteViews customContent = mBuilder.mN.bigContentView != null 9823 ? mBuilder.mN.bigContentView 9824 : mBuilder.mN.contentView; 9825 return makeMediaBigContentView(customContent); 9826 } 9827 9828 /** 9829 * @hide 9830 */ 9831 @Override makeHeadsUpContentView(boolean increasedHeight)9832 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9833 RemoteViews customContent = mBuilder.mN.headsUpContentView != null 9834 ? mBuilder.mN.headsUpContentView 9835 : mBuilder.mN.contentView; 9836 return makeMediaBigContentView(customContent); 9837 } 9838 9839 /** 9840 * @hide 9841 */ 9842 @Override areNotificationsVisiblyDifferent(Style other)9843 public boolean areNotificationsVisiblyDifferent(Style other) { 9844 if (other == null || getClass() != other.getClass()) { 9845 return true; 9846 } 9847 // Comparison done for all custom RemoteViews, independent of style 9848 return false; 9849 } 9850 } 9851 9852 /** 9853 * Encapsulates the information needed to display a notification as a bubble. 9854 * 9855 * <p>A bubble is used to display app content in a floating window over the existing 9856 * foreground activity. A bubble has a collapsed state represented by an icon and an 9857 * expanded state that displays an activity. These may be defined via 9858 * {@link Builder#Builder(PendingIntent, Icon)} or they may 9859 * be defined via an existing shortcut using {@link Builder#Builder(String)}. 9860 * </p> 9861 * 9862 * <b>Notifications with a valid and allowed bubble will display in collapsed state 9863 * outside of the notification shade on unlocked devices. When a user interacts with the 9864 * collapsed bubble, the bubble activity will be invoked and displayed.</b> 9865 * 9866 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 9867 */ 9868 public static final class BubbleMetadata implements Parcelable { 9869 9870 private PendingIntent mPendingIntent; 9871 private PendingIntent mDeleteIntent; 9872 private Icon mIcon; 9873 private int mDesiredHeight; 9874 @DimenRes private int mDesiredHeightResId; 9875 private int mFlags; 9876 private String mShortcutId; 9877 9878 /** 9879 * If set and the app creating the bubble is in the foreground, the bubble will be posted 9880 * in its expanded state. 9881 * 9882 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 9883 * The app is considered foreground if it is visible and on the screen, note that 9884 * a foreground service does not qualify. 9885 * </p> 9886 * 9887 * <p>Generally this flag should only be set if the user has performed an action to request 9888 * or create a bubble.</p> 9889 * 9890 * @hide 9891 */ 9892 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 9893 9894 /** 9895 * Indicates whether the notification associated with the bubble is being visually 9896 * suppressed from the notification shade. When <code>true</code> the notification is 9897 * hidden, when <code>false</code> the notification shows as normal. 9898 * 9899 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 9900 * the associated notification in the notification shade.</p> 9901 * 9902 * <p>Generally this flag should only be set by the app if the user has performed an 9903 * action to request or create a bubble, or if the user has seen the content in the 9904 * notification and the notification is no longer relevant. </p> 9905 * 9906 * <p>The system will also update this flag with <code>true</code> to hide the notification 9907 * from the user once the bubble has been expanded. </p> 9908 * 9909 * @hide 9910 */ 9911 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 9912 9913 /** 9914 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 9915 * user is viewing the same content outside of the bubble. For example, the user has a 9916 * bubble with Alice and then opens up the main app and navigates to Alice's page. 9917 * 9918 * @hide 9919 */ 9920 public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004; 9921 9922 /** 9923 * Indicates whether the bubble is visually suppressed from the bubble stack. 9924 * 9925 * @hide 9926 */ 9927 public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008; 9928 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)9929 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 9930 Icon icon, int height, @DimenRes int heightResId, String shortcutId) { 9931 mPendingIntent = expandIntent; 9932 mIcon = icon; 9933 mDesiredHeight = height; 9934 mDesiredHeightResId = heightResId; 9935 mDeleteIntent = deleteIntent; 9936 mShortcutId = shortcutId; 9937 } 9938 BubbleMetadata(Parcel in)9939 private BubbleMetadata(Parcel in) { 9940 if (in.readInt() != 0) { 9941 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 9942 } 9943 if (in.readInt() != 0) { 9944 mIcon = Icon.CREATOR.createFromParcel(in); 9945 } 9946 mDesiredHeight = in.readInt(); 9947 mFlags = in.readInt(); 9948 if (in.readInt() != 0) { 9949 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 9950 } 9951 mDesiredHeightResId = in.readInt(); 9952 if (in.readInt() != 0) { 9953 mShortcutId = in.readString8(); 9954 } 9955 } 9956 9957 /** 9958 * @return the shortcut id used for this bubble if created via 9959 * {@link Builder#Builder(String)} or null if created 9960 * via {@link Builder#Builder(PendingIntent, Icon)}. 9961 */ 9962 @Nullable getShortcutId()9963 public String getShortcutId() { 9964 return mShortcutId; 9965 } 9966 9967 /** 9968 * @return the pending intent used to populate the floating window for this bubble, or 9969 * null if this bubble is created via {@link Builder#Builder(String)}. 9970 */ 9971 @SuppressLint("InvalidNullConversion") 9972 @Nullable getIntent()9973 public PendingIntent getIntent() { 9974 return mPendingIntent; 9975 } 9976 9977 /** 9978 * @deprecated use {@link #getIntent()} instead. 9979 * @removed Removed from the R SDK but was never publicly stable. 9980 */ 9981 @Nullable 9982 @Deprecated getBubbleIntent()9983 public PendingIntent getBubbleIntent() { 9984 return mPendingIntent; 9985 } 9986 9987 /** 9988 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 9989 */ 9990 @Nullable getDeleteIntent()9991 public PendingIntent getDeleteIntent() { 9992 return mDeleteIntent; 9993 } 9994 9995 /** 9996 * @return the icon that will be displayed for this bubble when it is collapsed, or null 9997 * if the bubble is created via {@link Builder#Builder(String)}. 9998 */ 9999 @SuppressLint("InvalidNullConversion") 10000 @Nullable getIcon()10001 public Icon getIcon() { 10002 return mIcon; 10003 } 10004 10005 /** 10006 * @deprecated use {@link #getIcon()} instead. 10007 * @removed Removed from the R SDK but was never publicly stable. 10008 */ 10009 @Nullable 10010 @Deprecated getBubbleIcon()10011 public Icon getBubbleIcon() { 10012 return mIcon; 10013 } 10014 10015 /** 10016 * @return the ideal height, in DPs, for the floating window that app content defined by 10017 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has 10018 * not been set. 10019 */ 10020 @Dimension(unit = DP) getDesiredHeight()10021 public int getDesiredHeight() { 10022 return mDesiredHeight; 10023 } 10024 10025 /** 10026 * @return the resId of ideal height for the floating window that app content defined by 10027 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 10028 * been provided for the desired height. 10029 */ 10030 @DimenRes getDesiredHeightResId()10031 public int getDesiredHeightResId() { 10032 return mDesiredHeightResId; 10033 } 10034 10035 /** 10036 * @return whether this bubble should auto expand when it is posted. 10037 * 10038 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 10039 */ getAutoExpandBubble()10040 public boolean getAutoExpandBubble() { 10041 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 10042 } 10043 10044 /** 10045 * Indicates whether the notification associated with the bubble is being visually 10046 * suppressed from the notification shade. When <code>true</code> the notification is 10047 * hidden, when <code>false</code> the notification shows as normal. 10048 * 10049 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 10050 * the associated notification in the notification shade.</p> 10051 * 10052 * <p>Generally the app should only set this flag if the user has performed an 10053 * action to request or create a bubble, or if the user has seen the content in the 10054 * notification and the notification is no longer relevant. </p> 10055 * 10056 * <p>The system will update this flag with <code>true</code> to hide the notification 10057 * from the user once the bubble has been expanded.</p> 10058 * 10059 * @return whether this bubble should suppress the notification when it is posted. 10060 * 10061 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 10062 */ isNotificationSuppressed()10063 public boolean isNotificationSuppressed() { 10064 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 10065 } 10066 10067 /** 10068 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 10069 * user is viewing the same content outside of the bubble. For example, the user has a 10070 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10071 * 10072 * To match the activity and the bubble notification, the bubble notification should 10073 * have a locus id set that matches a locus id set on the activity. 10074 * 10075 * @return whether this bubble should be suppressed when the same content is visible 10076 * outside of the bubble. 10077 * 10078 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10079 */ isBubbleSuppressable()10080 public boolean isBubbleSuppressable() { 10081 return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0; 10082 } 10083 10084 /** 10085 * Indicates whether the bubble is currently visually suppressed from the bubble stack. 10086 * 10087 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10088 */ isBubbleSuppressed()10089 public boolean isBubbleSuppressed() { 10090 return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0; 10091 } 10092 10093 /** 10094 * Sets whether the notification associated with the bubble is being visually 10095 * suppressed from the notification shade. When <code>true</code> the notification is 10096 * hidden, when <code>false</code> the notification shows as normal. 10097 * 10098 * @hide 10099 */ setSuppressNotification(boolean suppressed)10100 public void setSuppressNotification(boolean suppressed) { 10101 if (suppressed) { 10102 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10103 } else { 10104 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10105 } 10106 } 10107 10108 /** 10109 * Sets whether the bubble should be visually suppressed from the bubble stack if the 10110 * user is viewing the same content outside of the bubble. For example, the user has a 10111 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10112 * 10113 * @hide 10114 */ setSuppressBubble(boolean suppressed)10115 public void setSuppressBubble(boolean suppressed) { 10116 if (suppressed) { 10117 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10118 } else { 10119 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10120 } 10121 } 10122 10123 /** 10124 * @hide 10125 */ setFlags(int flags)10126 public void setFlags(int flags) { 10127 mFlags = flags; 10128 } 10129 10130 /** 10131 * @hide 10132 */ getFlags()10133 public int getFlags() { 10134 return mFlags; 10135 } 10136 10137 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 10138 new Parcelable.Creator<BubbleMetadata>() { 10139 10140 @Override 10141 public BubbleMetadata createFromParcel(Parcel source) { 10142 return new BubbleMetadata(source); 10143 } 10144 10145 @Override 10146 public BubbleMetadata[] newArray(int size) { 10147 return new BubbleMetadata[size]; 10148 } 10149 }; 10150 10151 @Override describeContents()10152 public int describeContents() { 10153 return 0; 10154 } 10155 10156 @Override writeToParcel(Parcel out, int flags)10157 public void writeToParcel(Parcel out, int flags) { 10158 out.writeInt(mPendingIntent != null ? 1 : 0); 10159 if (mPendingIntent != null) { 10160 mPendingIntent.writeToParcel(out, 0); 10161 } 10162 out.writeInt(mIcon != null ? 1 : 0); 10163 if (mIcon != null) { 10164 mIcon.writeToParcel(out, 0); 10165 } 10166 out.writeInt(mDesiredHeight); 10167 out.writeInt(mFlags); 10168 out.writeInt(mDeleteIntent != null ? 1 : 0); 10169 if (mDeleteIntent != null) { 10170 mDeleteIntent.writeToParcel(out, 0); 10171 } 10172 out.writeInt(mDesiredHeightResId); 10173 out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); 10174 if (!TextUtils.isEmpty(mShortcutId)) { 10175 out.writeString8(mShortcutId); 10176 } 10177 } 10178 10179 /** 10180 * Builder to construct a {@link BubbleMetadata} object. 10181 */ 10182 public static final class Builder { 10183 10184 private PendingIntent mPendingIntent; 10185 private Icon mIcon; 10186 private int mDesiredHeight; 10187 @DimenRes private int mDesiredHeightResId; 10188 private int mFlags; 10189 private PendingIntent mDeleteIntent; 10190 private String mShortcutId; 10191 10192 /** 10193 * @deprecated use {@link Builder#Builder(String)} for a bubble created via a 10194 * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble 10195 * created via a {@link PendingIntent}. 10196 */ 10197 @Deprecated Builder()10198 public Builder() { 10199 } 10200 10201 /** 10202 * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create 10203 * a shortcut bubble, ensure that the shortcut associated with the provided 10204 * {@param shortcutId} is published as a dynamic shortcut that was built with 10205 * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your 10206 * notification will not be able to bubble. 10207 * 10208 * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p> 10209 * 10210 * <p>The shortcut activity will be used when the bubble is expanded. This will display 10211 * the shortcut activity in a floating window over the existing foreground activity.</p> 10212 * 10213 * <p>When the activity is launched from a bubble, 10214 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10215 * </p> 10216 * 10217 * <p>If the shortcut has not been published when the bubble notification is sent, 10218 * no bubble will be produced. If the shortcut is deleted while the bubble is active, 10219 * the bubble will be removed.</p> 10220 * 10221 * @throws NullPointerException if shortcutId is null. 10222 * 10223 * @see ShortcutInfo 10224 * @see ShortcutInfo.Builder#setLongLived(boolean) 10225 * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List) 10226 */ Builder(@onNull String shortcutId)10227 public Builder(@NonNull String shortcutId) { 10228 if (TextUtils.isEmpty(shortcutId)) { 10229 throw new NullPointerException("Bubble requires a non-null shortcut id"); 10230 } 10231 mShortcutId = shortcutId; 10232 } 10233 10234 /** 10235 * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon. 10236 * 10237 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10238 * should be representative of the content within the bubble. If your app produces 10239 * multiple bubbles, the icon should be unique for each of them.</p> 10240 * 10241 * <p>The intent that will be used when the bubble is expanded. This will display the 10242 * app content in a floating window over the existing foreground activity. The intent 10243 * should point to a resizable activity. </p> 10244 * 10245 * <p>When the activity is launched from a bubble, 10246 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10247 * </p> 10248 * 10249 * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE. 10250 * 10251 * @throws NullPointerException if intent is null. 10252 * @throws NullPointerException if icon is null. 10253 */ Builder(@onNull PendingIntent intent, @NonNull Icon icon)10254 public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) { 10255 if (intent == null) { 10256 throw new NullPointerException("Bubble requires non-null pending intent"); 10257 } 10258 if (icon == null) { 10259 throw new NullPointerException("Bubbles require non-null icon"); 10260 } 10261 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10262 && icon.getType() != TYPE_URI) { 10263 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10264 + "TYPE_URI_ADAPTIVE_BITMAP. " 10265 + "In the future, using an icon of this type will be required."); 10266 } 10267 mPendingIntent = intent; 10268 mIcon = icon; 10269 } 10270 10271 /** 10272 * @deprecated use {@link Builder#Builder(String)} instead. 10273 * @removed Removed from the R SDK but was never publicly stable. 10274 */ 10275 @NonNull 10276 @Deprecated createShortcutBubble(@onNull String shortcutId)10277 public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) { 10278 if (!TextUtils.isEmpty(shortcutId)) { 10279 // If shortcut id is set, we don't use these if they were previously set. 10280 mPendingIntent = null; 10281 mIcon = null; 10282 } 10283 mShortcutId = shortcutId; 10284 return this; 10285 } 10286 10287 /** 10288 * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead. 10289 * @removed Removed from the R SDK but was never publicly stable. 10290 */ 10291 @NonNull 10292 @Deprecated createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10293 public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent, 10294 @NonNull Icon icon) { 10295 if (intent == null) { 10296 throw new IllegalArgumentException("Bubble requires non-null pending intent"); 10297 } 10298 if (icon == null) { 10299 throw new IllegalArgumentException("Bubbles require non-null icon"); 10300 } 10301 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10302 && icon.getType() != TYPE_URI) { 10303 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10304 + "TYPE_URI_ADAPTIVE_BITMAP. " 10305 + "In the future, using an icon of this type will be required."); 10306 } 10307 mShortcutId = null; 10308 mPendingIntent = intent; 10309 mIcon = icon; 10310 return this; 10311 } 10312 10313 /** 10314 * Sets the intent for the bubble. 10315 * 10316 * <p>The intent that will be used when the bubble is expanded. This will display the 10317 * app content in a floating window over the existing foreground activity. The intent 10318 * should point to a resizable activity. </p> 10319 * 10320 * @throws NullPointerException if intent is null. 10321 * @throws IllegalStateException if this builder was created via 10322 * {@link Builder#Builder(String)}. 10323 */ 10324 @NonNull setIntent(@onNull PendingIntent intent)10325 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 10326 if (mShortcutId != null) { 10327 throw new IllegalStateException("Created as a shortcut bubble, cannot set a " 10328 + "PendingIntent. Consider using " 10329 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10330 } 10331 if (intent == null) { 10332 throw new NullPointerException("Bubble requires non-null pending intent"); 10333 } 10334 mPendingIntent = intent; 10335 return this; 10336 } 10337 10338 /** 10339 * Sets the icon for the bubble. Can only be used if the bubble was created 10340 * via {@link Builder#Builder(PendingIntent, Icon)}. 10341 * 10342 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10343 * should be representative of the content within the bubble. If your app produces 10344 * multiple bubbles, the icon should be unique for each of them.</p> 10345 * 10346 * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI} 10347 * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p> 10348 * 10349 * @throws NullPointerException if icon is null. 10350 * @throws IllegalStateException if this builder was created via 10351 * {@link Builder#Builder(String)}. 10352 */ 10353 @NonNull setIcon(@onNull Icon icon)10354 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 10355 if (mShortcutId != null) { 10356 throw new IllegalStateException("Created as a shortcut bubble, cannot set an " 10357 + "Icon. Consider using " 10358 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10359 } 10360 if (icon == null) { 10361 throw new NullPointerException("Bubbles require non-null icon"); 10362 } 10363 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10364 && icon.getType() != TYPE_URI) { 10365 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10366 + "TYPE_URI_ADAPTIVE_BITMAP. " 10367 + "In the future, using an icon of this type will be required."); 10368 } 10369 mIcon = icon; 10370 return this; 10371 } 10372 10373 /** 10374 * Sets the desired height in DPs for the expanded content of the bubble. 10375 * 10376 * <p>This height may not be respected if there is not enough space on the screen or if 10377 * the provided height is too small to be useful.</p> 10378 * 10379 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 10380 * previous value set will be cleared after calling this method, and this value will 10381 * be used instead.</p> 10382 * 10383 * <p>A desired height (in DPs or via resID) is optional.</p> 10384 * 10385 * @see #setDesiredHeightResId(int) 10386 */ 10387 @NonNull setDesiredHeight(@imensionunit = DP) int height)10388 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 10389 mDesiredHeight = Math.max(height, 0); 10390 mDesiredHeightResId = 0; 10391 return this; 10392 } 10393 10394 10395 /** 10396 * Sets the desired height via resId for the expanded content of the bubble. 10397 * 10398 * <p>This height may not be respected if there is not enough space on the screen or if 10399 * the provided height is too small to be useful.</p> 10400 * 10401 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 10402 * previous value set will be cleared after calling this method, and this value will 10403 * be used instead.</p> 10404 * 10405 * <p>A desired height (in DPs or via resID) is optional.</p> 10406 * 10407 * @see #setDesiredHeight(int) 10408 */ 10409 @NonNull setDesiredHeightResId(@imenRes int heightResId)10410 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 10411 mDesiredHeightResId = heightResId; 10412 mDesiredHeight = 0; 10413 return this; 10414 } 10415 10416 /** 10417 * Sets whether the bubble will be posted in its expanded state. 10418 * 10419 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 10420 * The app is considered foreground if it is visible and on the screen, note that 10421 * a foreground service does not qualify. 10422 * </p> 10423 * 10424 * <p>Generally, this flag should only be set if the user has performed an action to 10425 * request or create a bubble.</p> 10426 * 10427 * <p>Setting this flag is optional; it defaults to false.</p> 10428 */ 10429 @NonNull setAutoExpandBubble(boolean shouldExpand)10430 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 10431 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 10432 return this; 10433 } 10434 10435 /** 10436 * Sets whether the bubble will be posted <b>without</b> the associated notification in 10437 * the notification shade. 10438 * 10439 * <p>Generally, this flag should only be set if the user has performed an action to 10440 * request or create a bubble, or if the user has seen the content in the notification 10441 * and the notification is no longer relevant.</p> 10442 * 10443 * <p>Setting this flag is optional; it defaults to false.</p> 10444 */ 10445 @NonNull setSuppressNotification(boolean shouldSuppressNotif)10446 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 10447 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 10448 return this; 10449 } 10450 10451 /** 10452 * Indicates whether the bubble should be visually suppressed from the bubble stack if 10453 * the user is viewing the same content outside of the bubble. For example, the user has 10454 * a bubble with Alice and then opens up the main app and navigates to Alice's page. 10455 * 10456 * To match the activity and the bubble notification, the bubble notification should 10457 * have a locus id set that matches a locus id set on the activity. 10458 * 10459 * {@link Notification.Builder#setLocusId(LocusId)} 10460 * {@link Activity#setLocusContext(LocusId, Bundle)} 10461 */ 10462 @NonNull setSuppressableBubble(boolean suppressBubble)10463 public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) { 10464 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble); 10465 return this; 10466 } 10467 10468 /** 10469 * Sets an intent to send when this bubble is explicitly removed by the user. 10470 * 10471 * <p>Setting a delete intent is optional.</p> 10472 */ 10473 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)10474 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 10475 mDeleteIntent = deleteIntent; 10476 return this; 10477 } 10478 10479 /** 10480 * Creates the {@link BubbleMetadata} defined by this builder. 10481 * 10482 * @throws NullPointerException if required elements have not been set. 10483 */ 10484 @NonNull build()10485 public BubbleMetadata build() { 10486 if (mShortcutId == null && mPendingIntent == null) { 10487 throw new NullPointerException( 10488 "Must supply pending intent or shortcut to bubble"); 10489 } 10490 if (mShortcutId == null && mIcon == null) { 10491 throw new NullPointerException( 10492 "Must supply an icon or shortcut for the bubble"); 10493 } 10494 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 10495 mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId); 10496 data.setFlags(mFlags); 10497 return data; 10498 } 10499 10500 /** 10501 * @hide 10502 */ setFlag(int mask, boolean value)10503 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 10504 if (value) { 10505 mFlags |= mask; 10506 } else { 10507 mFlags &= ~mask; 10508 } 10509 return this; 10510 } 10511 } 10512 } 10513 10514 10515 // When adding a new Style subclass here, don't forget to update 10516 // Builder.getNotificationStyleClass. 10517 10518 /** 10519 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 10520 * metadata or change options on a notification builder. 10521 */ 10522 public interface Extender { 10523 /** 10524 * Apply this extender to a notification builder. 10525 * @param builder the builder to be modified. 10526 * @return the build object for chaining. 10527 */ extend(Builder builder)10528 public Builder extend(Builder builder); 10529 } 10530 10531 /** 10532 * Helper class to add wearable extensions to notifications. 10533 * <p class="note"> See 10534 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 10535 * for Android Wear</a> for more information on how to use this class. 10536 * <p> 10537 * To create a notification with wearable extensions: 10538 * <ol> 10539 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 10540 * properties. 10541 * <li>Create a {@link android.app.Notification.WearableExtender}. 10542 * <li>Set wearable-specific properties using the 10543 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 10544 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 10545 * notification. 10546 * <li>Post the notification to the notification system with the 10547 * {@code NotificationManager.notify(...)} methods. 10548 * </ol> 10549 * 10550 * <pre class="prettyprint"> 10551 * Notification notif = new Notification.Builder(mContext) 10552 * .setContentTitle("New mail from " + sender.toString()) 10553 * .setContentText(subject) 10554 * .setSmallIcon(R.drawable.new_mail) 10555 * .extend(new Notification.WearableExtender() 10556 * .setContentIcon(R.drawable.new_mail)) 10557 * .build(); 10558 * NotificationManager notificationManger = 10559 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 10560 * notificationManger.notify(0, notif);</pre> 10561 * 10562 * <p>Wearable extensions can be accessed on an existing notification by using the 10563 * {@code WearableExtender(Notification)} constructor, 10564 * and then using the {@code get} methods to access values. 10565 * 10566 * <pre class="prettyprint"> 10567 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 10568 * notification); 10569 * List<Notification> pages = wearableExtender.getPages();</pre> 10570 */ 10571 public static final class WearableExtender implements Extender { 10572 /** 10573 * Sentinel value for an action index that is unset. 10574 */ 10575 public static final int UNSET_ACTION_INDEX = -1; 10576 10577 /** 10578 * Size value for use with {@link #setCustomSizePreset} to show this notification with 10579 * default sizing. 10580 * <p>For custom display notifications created using {@link #setDisplayIntent}, 10581 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 10582 * on their content. 10583 * 10584 * @deprecated Display intents are no longer supported. 10585 */ 10586 @Deprecated 10587 public static final int SIZE_DEFAULT = 0; 10588 10589 /** 10590 * Size value for use with {@link #setCustomSizePreset} to show this notification 10591 * with an extra small size. 10592 * <p>This value is only applicable for custom display notifications created using 10593 * {@link #setDisplayIntent}. 10594 * 10595 * @deprecated Display intents are no longer supported. 10596 */ 10597 @Deprecated 10598 public static final int SIZE_XSMALL = 1; 10599 10600 /** 10601 * Size value for use with {@link #setCustomSizePreset} to show this notification 10602 * with a small size. 10603 * <p>This value is only applicable for custom display notifications created using 10604 * {@link #setDisplayIntent}. 10605 * 10606 * @deprecated Display intents are no longer supported. 10607 */ 10608 @Deprecated 10609 public static final int SIZE_SMALL = 2; 10610 10611 /** 10612 * Size value for use with {@link #setCustomSizePreset} to show this notification 10613 * with a medium size. 10614 * <p>This value is only applicable for custom display notifications created using 10615 * {@link #setDisplayIntent}. 10616 * 10617 * @deprecated Display intents are no longer supported. 10618 */ 10619 @Deprecated 10620 public static final int SIZE_MEDIUM = 3; 10621 10622 /** 10623 * Size value for use with {@link #setCustomSizePreset} to show this notification 10624 * with a large size. 10625 * <p>This value is only applicable for custom display notifications created using 10626 * {@link #setDisplayIntent}. 10627 * 10628 * @deprecated Display intents are no longer supported. 10629 */ 10630 @Deprecated 10631 public static final int SIZE_LARGE = 4; 10632 10633 /** 10634 * Size value for use with {@link #setCustomSizePreset} to show this notification 10635 * full screen. 10636 * <p>This value is only applicable for custom display notifications created using 10637 * {@link #setDisplayIntent}. 10638 * 10639 * @deprecated Display intents are no longer supported. 10640 */ 10641 @Deprecated 10642 public static final int SIZE_FULL_SCREEN = 5; 10643 10644 /** 10645 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 10646 * short amount of time when this notification is displayed on the screen. This 10647 * is the default value. 10648 * 10649 * @deprecated This feature is no longer supported. 10650 */ 10651 @Deprecated 10652 public static final int SCREEN_TIMEOUT_SHORT = 0; 10653 10654 /** 10655 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 10656 * for a longer amount of time when this notification is displayed on the screen. 10657 * 10658 * @deprecated This feature is no longer supported. 10659 */ 10660 @Deprecated 10661 public static final int SCREEN_TIMEOUT_LONG = -1; 10662 10663 /** Notification extra which contains wearable extensions */ 10664 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 10665 10666 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 10667 private static final String KEY_ACTIONS = "actions"; 10668 private static final String KEY_FLAGS = "flags"; 10669 private static final String KEY_DISPLAY_INTENT = "displayIntent"; 10670 private static final String KEY_PAGES = "pages"; 10671 private static final String KEY_BACKGROUND = "background"; 10672 private static final String KEY_CONTENT_ICON = "contentIcon"; 10673 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 10674 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 10675 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 10676 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 10677 private static final String KEY_GRAVITY = "gravity"; 10678 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 10679 private static final String KEY_DISMISSAL_ID = "dismissalId"; 10680 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 10681 10682 // Flags bitwise-ored to mFlags 10683 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 10684 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 10685 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 10686 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 10687 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 10688 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 10689 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 10690 10691 // Default value for flags integer 10692 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 10693 10694 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 10695 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 10696 10697 private ArrayList<Action> mActions = new ArrayList<Action>(); 10698 private int mFlags = DEFAULT_FLAGS; 10699 private PendingIntent mDisplayIntent; 10700 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 10701 private Bitmap mBackground; 10702 private int mContentIcon; 10703 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 10704 private int mContentActionIndex = UNSET_ACTION_INDEX; 10705 private int mCustomSizePreset = SIZE_DEFAULT; 10706 private int mCustomContentHeight; 10707 private int mGravity = DEFAULT_GRAVITY; 10708 private int mHintScreenTimeout; 10709 private String mDismissalId; 10710 private String mBridgeTag; 10711 10712 /** 10713 * Create a {@link android.app.Notification.WearableExtender} with default 10714 * options. 10715 */ WearableExtender()10716 public WearableExtender() { 10717 } 10718 WearableExtender(Notification notif)10719 public WearableExtender(Notification notif) { 10720 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 10721 if (wearableBundle != null) { 10722 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 10723 if (actions != null) { 10724 mActions.addAll(actions); 10725 } 10726 10727 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 10728 mDisplayIntent = wearableBundle.getParcelable(KEY_DISPLAY_INTENT); 10729 10730 Notification[] pages = getParcelableArrayFromBundle( 10731 wearableBundle, KEY_PAGES, Notification.class); 10732 if (pages != null) { 10733 Collections.addAll(mPages, pages); 10734 } 10735 10736 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND); 10737 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 10738 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 10739 DEFAULT_CONTENT_ICON_GRAVITY); 10740 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 10741 UNSET_ACTION_INDEX); 10742 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 10743 SIZE_DEFAULT); 10744 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 10745 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 10746 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 10747 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 10748 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 10749 } 10750 } 10751 10752 /** 10753 * Apply wearable extensions to a notification that is being built. This is typically 10754 * called by the {@link android.app.Notification.Builder#extend} method of 10755 * {@link android.app.Notification.Builder}. 10756 */ 10757 @Override extend(Notification.Builder builder)10758 public Notification.Builder extend(Notification.Builder builder) { 10759 Bundle wearableBundle = new Bundle(); 10760 10761 if (!mActions.isEmpty()) { 10762 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 10763 } 10764 if (mFlags != DEFAULT_FLAGS) { 10765 wearableBundle.putInt(KEY_FLAGS, mFlags); 10766 } 10767 if (mDisplayIntent != null) { 10768 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 10769 } 10770 if (!mPages.isEmpty()) { 10771 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 10772 new Notification[mPages.size()])); 10773 } 10774 if (mBackground != null) { 10775 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 10776 } 10777 if (mContentIcon != 0) { 10778 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 10779 } 10780 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 10781 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 10782 } 10783 if (mContentActionIndex != UNSET_ACTION_INDEX) { 10784 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 10785 mContentActionIndex); 10786 } 10787 if (mCustomSizePreset != SIZE_DEFAULT) { 10788 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 10789 } 10790 if (mCustomContentHeight != 0) { 10791 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 10792 } 10793 if (mGravity != DEFAULT_GRAVITY) { 10794 wearableBundle.putInt(KEY_GRAVITY, mGravity); 10795 } 10796 if (mHintScreenTimeout != 0) { 10797 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 10798 } 10799 if (mDismissalId != null) { 10800 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 10801 } 10802 if (mBridgeTag != null) { 10803 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 10804 } 10805 10806 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 10807 return builder; 10808 } 10809 10810 @Override clone()10811 public WearableExtender clone() { 10812 WearableExtender that = new WearableExtender(); 10813 that.mActions = new ArrayList<Action>(this.mActions); 10814 that.mFlags = this.mFlags; 10815 that.mDisplayIntent = this.mDisplayIntent; 10816 that.mPages = new ArrayList<Notification>(this.mPages); 10817 that.mBackground = this.mBackground; 10818 that.mContentIcon = this.mContentIcon; 10819 that.mContentIconGravity = this.mContentIconGravity; 10820 that.mContentActionIndex = this.mContentActionIndex; 10821 that.mCustomSizePreset = this.mCustomSizePreset; 10822 that.mCustomContentHeight = this.mCustomContentHeight; 10823 that.mGravity = this.mGravity; 10824 that.mHintScreenTimeout = this.mHintScreenTimeout; 10825 that.mDismissalId = this.mDismissalId; 10826 that.mBridgeTag = this.mBridgeTag; 10827 return that; 10828 } 10829 10830 /** 10831 * Add a wearable action to this notification. 10832 * 10833 * <p>When wearable actions are added using this method, the set of actions that 10834 * show on a wearable device splits from devices that only show actions added 10835 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 10836 * of which actions display on different devices. 10837 * 10838 * @param action the action to add to this notification 10839 * @return this object for method chaining 10840 * @see android.app.Notification.Action 10841 */ addAction(Action action)10842 public WearableExtender addAction(Action action) { 10843 mActions.add(action); 10844 return this; 10845 } 10846 10847 /** 10848 * Adds wearable actions to this notification. 10849 * 10850 * <p>When wearable actions are added using this method, the set of actions that 10851 * show on a wearable device splits from devices that only show actions added 10852 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 10853 * of which actions display on different devices. 10854 * 10855 * @param actions the actions to add to this notification 10856 * @return this object for method chaining 10857 * @see android.app.Notification.Action 10858 */ addActions(List<Action> actions)10859 public WearableExtender addActions(List<Action> actions) { 10860 mActions.addAll(actions); 10861 return this; 10862 } 10863 10864 /** 10865 * Clear all wearable actions present on this builder. 10866 * @return this object for method chaining. 10867 * @see #addAction 10868 */ clearActions()10869 public WearableExtender clearActions() { 10870 mActions.clear(); 10871 return this; 10872 } 10873 10874 /** 10875 * Get the wearable actions present on this notification. 10876 */ getActions()10877 public List<Action> getActions() { 10878 return mActions; 10879 } 10880 10881 /** 10882 * Set an intent to launch inside of an activity view when displaying 10883 * this notification. The {@link PendingIntent} provided should be for an activity. 10884 * 10885 * <pre class="prettyprint"> 10886 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 10887 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 10888 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 10889 * Notification notif = new Notification.Builder(context) 10890 * .extend(new Notification.WearableExtender() 10891 * .setDisplayIntent(displayPendingIntent) 10892 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 10893 * .build();</pre> 10894 * 10895 * <p>The activity to launch needs to allow embedding, must be exported, and 10896 * should have an empty task affinity. It is also recommended to use the device 10897 * default light theme. 10898 * 10899 * <p>Example AndroidManifest.xml entry: 10900 * <pre class="prettyprint"> 10901 * <activity android:name="com.example.MyDisplayActivity" 10902 * android:exported="true" 10903 * android:allowEmbedded="true" 10904 * android:taskAffinity="" 10905 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 10906 * 10907 * @param intent the {@link PendingIntent} for an activity 10908 * @return this object for method chaining 10909 * @see android.app.Notification.WearableExtender#getDisplayIntent 10910 * @deprecated Display intents are no longer supported. 10911 */ 10912 @Deprecated setDisplayIntent(PendingIntent intent)10913 public WearableExtender setDisplayIntent(PendingIntent intent) { 10914 mDisplayIntent = intent; 10915 return this; 10916 } 10917 10918 /** 10919 * Get the intent to launch inside of an activity view when displaying this 10920 * notification. This {@code PendingIntent} should be for an activity. 10921 * 10922 * @deprecated Display intents are no longer supported. 10923 */ 10924 @Deprecated getDisplayIntent()10925 public PendingIntent getDisplayIntent() { 10926 return mDisplayIntent; 10927 } 10928 10929 /** 10930 * Add an additional page of content to display with this notification. The current 10931 * notification forms the first page, and pages added using this function form 10932 * subsequent pages. This field can be used to separate a notification into multiple 10933 * sections. 10934 * 10935 * @param page the notification to add as another page 10936 * @return this object for method chaining 10937 * @see android.app.Notification.WearableExtender#getPages 10938 * @deprecated Multiple content pages are no longer supported. 10939 */ 10940 @Deprecated addPage(Notification page)10941 public WearableExtender addPage(Notification page) { 10942 mPages.add(page); 10943 return this; 10944 } 10945 10946 /** 10947 * Add additional pages of content to display with this notification. The current 10948 * notification forms the first page, and pages added using this function form 10949 * subsequent pages. This field can be used to separate a notification into multiple 10950 * sections. 10951 * 10952 * @param pages a list of notifications 10953 * @return this object for method chaining 10954 * @see android.app.Notification.WearableExtender#getPages 10955 * @deprecated Multiple content pages are no longer supported. 10956 */ 10957 @Deprecated addPages(List<Notification> pages)10958 public WearableExtender addPages(List<Notification> pages) { 10959 mPages.addAll(pages); 10960 return this; 10961 } 10962 10963 /** 10964 * Clear all additional pages present on this builder. 10965 * @return this object for method chaining. 10966 * @see #addPage 10967 * @deprecated Multiple content pages are no longer supported. 10968 */ 10969 @Deprecated clearPages()10970 public WearableExtender clearPages() { 10971 mPages.clear(); 10972 return this; 10973 } 10974 10975 /** 10976 * Get the array of additional pages of content for displaying this notification. The 10977 * current notification forms the first page, and elements within this array form 10978 * subsequent pages. This field can be used to separate a notification into multiple 10979 * sections. 10980 * @return the pages for this notification 10981 * @deprecated Multiple content pages are no longer supported. 10982 */ 10983 @Deprecated getPages()10984 public List<Notification> getPages() { 10985 return mPages; 10986 } 10987 10988 /** 10989 * Set a background image to be displayed behind the notification content. 10990 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 10991 * will work with any notification style. 10992 * 10993 * @param background the background bitmap 10994 * @return this object for method chaining 10995 * @see android.app.Notification.WearableExtender#getBackground 10996 * @deprecated Background images are no longer supported. 10997 */ 10998 @Deprecated setBackground(Bitmap background)10999 public WearableExtender setBackground(Bitmap background) { 11000 mBackground = background; 11001 return this; 11002 } 11003 11004 /** 11005 * Get a background image to be displayed behind the notification content. 11006 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 11007 * will work with any notification style. 11008 * 11009 * @return the background image 11010 * @see android.app.Notification.WearableExtender#setBackground 11011 * @deprecated Background images are no longer supported. 11012 */ 11013 @Deprecated getBackground()11014 public Bitmap getBackground() { 11015 return mBackground; 11016 } 11017 11018 /** 11019 * Set an icon that goes with the content of this notification. 11020 */ 11021 @Deprecated setContentIcon(int icon)11022 public WearableExtender setContentIcon(int icon) { 11023 mContentIcon = icon; 11024 return this; 11025 } 11026 11027 /** 11028 * Get an icon that goes with the content of this notification. 11029 */ 11030 @Deprecated getContentIcon()11031 public int getContentIcon() { 11032 return mContentIcon; 11033 } 11034 11035 /** 11036 * Set the gravity that the content icon should have within the notification display. 11037 * Supported values include {@link android.view.Gravity#START} and 11038 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 11039 * @see #setContentIcon 11040 */ 11041 @Deprecated setContentIconGravity(int contentIconGravity)11042 public WearableExtender setContentIconGravity(int contentIconGravity) { 11043 mContentIconGravity = contentIconGravity; 11044 return this; 11045 } 11046 11047 /** 11048 * Get the gravity that the content icon should have within the notification display. 11049 * Supported values include {@link android.view.Gravity#START} and 11050 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 11051 * @see #getContentIcon 11052 */ 11053 @Deprecated getContentIconGravity()11054 public int getContentIconGravity() { 11055 return mContentIconGravity; 11056 } 11057 11058 /** 11059 * Set an action from this notification's actions as the primary action. If the action has a 11060 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 11061 * directly on the notification. 11062 * 11063 * @param actionIndex The index of the primary action. 11064 * If wearable actions were added to the main notification, this index 11065 * will apply to that list, otherwise it will apply to the regular 11066 * actions list. 11067 */ setContentAction(int actionIndex)11068 public WearableExtender setContentAction(int actionIndex) { 11069 mContentActionIndex = actionIndex; 11070 return this; 11071 } 11072 11073 /** 11074 * Get the index of the notification action, if any, that was specified as the primary 11075 * action. 11076 * 11077 * <p>If wearable specific actions were added to the main notification, this index will 11078 * apply to that list, otherwise it will apply to the regular actions list. 11079 * 11080 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 11081 */ getContentAction()11082 public int getContentAction() { 11083 return mContentActionIndex; 11084 } 11085 11086 /** 11087 * Set the gravity that this notification should have within the available viewport space. 11088 * Supported values include {@link android.view.Gravity#TOP}, 11089 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11090 * The default value is {@link android.view.Gravity#BOTTOM}. 11091 */ 11092 @Deprecated setGravity(int gravity)11093 public WearableExtender setGravity(int gravity) { 11094 mGravity = gravity; 11095 return this; 11096 } 11097 11098 /** 11099 * Get the gravity that this notification should have within the available viewport space. 11100 * Supported values include {@link android.view.Gravity#TOP}, 11101 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11102 * The default value is {@link android.view.Gravity#BOTTOM}. 11103 */ 11104 @Deprecated getGravity()11105 public int getGravity() { 11106 return mGravity; 11107 } 11108 11109 /** 11110 * Set the custom size preset for the display of this notification out of the available 11111 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11112 * {@link #SIZE_LARGE}. 11113 * <p>Some custom size presets are only applicable for custom display notifications created 11114 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 11115 * documentation for the preset in question. See also 11116 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 11117 */ 11118 @Deprecated setCustomSizePreset(int sizePreset)11119 public WearableExtender setCustomSizePreset(int sizePreset) { 11120 mCustomSizePreset = sizePreset; 11121 return this; 11122 } 11123 11124 /** 11125 * Get the custom size preset for the display of this notification out of the available 11126 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11127 * {@link #SIZE_LARGE}. 11128 * <p>Some custom size presets are only applicable for custom display notifications created 11129 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 11130 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 11131 */ 11132 @Deprecated getCustomSizePreset()11133 public int getCustomSizePreset() { 11134 return mCustomSizePreset; 11135 } 11136 11137 /** 11138 * Set the custom height in pixels for the display of this notification's content. 11139 * <p>This option is only available for custom display notifications created 11140 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 11141 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 11142 * {@link #getCustomContentHeight}. 11143 */ 11144 @Deprecated setCustomContentHeight(int height)11145 public WearableExtender setCustomContentHeight(int height) { 11146 mCustomContentHeight = height; 11147 return this; 11148 } 11149 11150 /** 11151 * Get the custom height in pixels for the display of this notification's content. 11152 * <p>This option is only available for custom display notifications created 11153 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 11154 * {@link #setCustomContentHeight}. 11155 */ 11156 @Deprecated getCustomContentHeight()11157 public int getCustomContentHeight() { 11158 return mCustomContentHeight; 11159 } 11160 11161 /** 11162 * Set whether the scrolling position for the contents of this notification should start 11163 * at the bottom of the contents instead of the top when the contents are too long to 11164 * display within the screen. Default is false (start scroll at the top). 11165 */ setStartScrollBottom(boolean startScrollBottom)11166 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 11167 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 11168 return this; 11169 } 11170 11171 /** 11172 * Get whether the scrolling position for the contents of this notification should start 11173 * at the bottom of the contents instead of the top when the contents are too long to 11174 * display within the screen. Default is false (start scroll at the top). 11175 */ getStartScrollBottom()11176 public boolean getStartScrollBottom() { 11177 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 11178 } 11179 11180 /** 11181 * Set whether the content intent is available when the wearable device is not connected 11182 * to a companion device. The user can still trigger this intent when the wearable device 11183 * is offline, but a visual hint will indicate that the content intent may not be available. 11184 * Defaults to true. 11185 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11186 public WearableExtender setContentIntentAvailableOffline( 11187 boolean contentIntentAvailableOffline) { 11188 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 11189 return this; 11190 } 11191 11192 /** 11193 * Get whether the content intent is available when the wearable device is not connected 11194 * to a companion device. The user can still trigger this intent when the wearable device 11195 * is offline, but a visual hint will indicate that the content intent may not be available. 11196 * Defaults to true. 11197 */ getContentIntentAvailableOffline()11198 public boolean getContentIntentAvailableOffline() { 11199 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 11200 } 11201 11202 /** 11203 * Set a hint that this notification's icon should not be displayed. 11204 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 11205 * @return this object for method chaining 11206 */ 11207 @Deprecated setHintHideIcon(boolean hintHideIcon)11208 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 11209 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 11210 return this; 11211 } 11212 11213 /** 11214 * Get a hint that this notification's icon should not be displayed. 11215 * @return {@code true} if this icon should not be displayed, false otherwise. 11216 * The default value is {@code false} if this was never set. 11217 */ 11218 @Deprecated getHintHideIcon()11219 public boolean getHintHideIcon() { 11220 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 11221 } 11222 11223 /** 11224 * Set a visual hint that only the background image of this notification should be 11225 * displayed, and other semantic content should be hidden. This hint is only applicable 11226 * to sub-pages added using {@link #addPage}. 11227 */ 11228 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11229 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 11230 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 11231 return this; 11232 } 11233 11234 /** 11235 * Get a visual hint that only the background image of this notification should be 11236 * displayed, and other semantic content should be hidden. This hint is only applicable 11237 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 11238 */ 11239 @Deprecated getHintShowBackgroundOnly()11240 public boolean getHintShowBackgroundOnly() { 11241 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 11242 } 11243 11244 /** 11245 * Set a hint that this notification's background should not be clipped if possible, 11246 * and should instead be resized to fully display on the screen, retaining the aspect 11247 * ratio of the image. This can be useful for images like barcodes or qr codes. 11248 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 11249 * @return this object for method chaining 11250 */ 11251 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11252 public WearableExtender setHintAvoidBackgroundClipping( 11253 boolean hintAvoidBackgroundClipping) { 11254 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 11255 return this; 11256 } 11257 11258 /** 11259 * Get a hint that this notification's background should not be clipped if possible, 11260 * and should instead be resized to fully display on the screen, retaining the aspect 11261 * ratio of the image. This can be useful for images like barcodes or qr codes. 11262 * @return {@code true} if it's ok if the background is clipped on the screen, false 11263 * otherwise. The default value is {@code false} if this was never set. 11264 */ 11265 @Deprecated getHintAvoidBackgroundClipping()11266 public boolean getHintAvoidBackgroundClipping() { 11267 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 11268 } 11269 11270 /** 11271 * Set a hint that the screen should remain on for at least this duration when 11272 * this notification is displayed on the screen. 11273 * @param timeout The requested screen timeout in milliseconds. Can also be either 11274 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11275 * @return this object for method chaining 11276 */ 11277 @Deprecated setHintScreenTimeout(int timeout)11278 public WearableExtender setHintScreenTimeout(int timeout) { 11279 mHintScreenTimeout = timeout; 11280 return this; 11281 } 11282 11283 /** 11284 * Get the duration, in milliseconds, that the screen should remain on for 11285 * when this notification is displayed. 11286 * @return the duration in milliseconds if > 0, or either one of the sentinel values 11287 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11288 */ 11289 @Deprecated getHintScreenTimeout()11290 public int getHintScreenTimeout() { 11291 return mHintScreenTimeout; 11292 } 11293 11294 /** 11295 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 11296 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11297 * qr codes, as well as other simple black-and-white tickets. 11298 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 11299 * @return this object for method chaining 11300 * @deprecated This feature is no longer supported. 11301 */ 11302 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)11303 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 11304 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 11305 return this; 11306 } 11307 11308 /** 11309 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 11310 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11311 * qr codes, as well as other simple black-and-white tickets. 11312 * @return {@code true} if it should be displayed in ambient, false otherwise 11313 * otherwise. The default value is {@code false} if this was never set. 11314 * @deprecated This feature is no longer supported. 11315 */ 11316 @Deprecated getHintAmbientBigPicture()11317 public boolean getHintAmbientBigPicture() { 11318 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 11319 } 11320 11321 /** 11322 * Set a hint that this notification's content intent will launch an {@link Activity} 11323 * directly, telling the platform that it can generate the appropriate transitions. 11324 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 11325 * an activity and transitions should be generated, false otherwise. 11326 * @return this object for method chaining 11327 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11328 public WearableExtender setHintContentIntentLaunchesActivity( 11329 boolean hintContentIntentLaunchesActivity) { 11330 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 11331 return this; 11332 } 11333 11334 /** 11335 * Get a hint that this notification's content intent will launch an {@link Activity} 11336 * directly, telling the platform that it can generate the appropriate transitions 11337 * @return {@code true} if the content intent will launch an activity and transitions should 11338 * be generated, false otherwise. The default value is {@code false} if this was never set. 11339 */ getHintContentIntentLaunchesActivity()11340 public boolean getHintContentIntentLaunchesActivity() { 11341 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 11342 } 11343 11344 /** 11345 * Sets the dismissal id for this notification. If a notification is posted with a 11346 * dismissal id, then when that notification is canceled, notifications on other wearables 11347 * and the paired Android phone having that same dismissal id will also be canceled. See 11348 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 11349 * Notifications</a> for more information. 11350 * @param dismissalId the dismissal id of the notification. 11351 * @return this object for method chaining 11352 */ setDismissalId(String dismissalId)11353 public WearableExtender setDismissalId(String dismissalId) { 11354 mDismissalId = dismissalId; 11355 return this; 11356 } 11357 11358 /** 11359 * Returns the dismissal id of the notification. 11360 * @return the dismissal id of the notification or null if it has not been set. 11361 */ getDismissalId()11362 public String getDismissalId() { 11363 return mDismissalId; 11364 } 11365 11366 /** 11367 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 11368 * posted from a phone to provide finer-grained control on what notifications are bridged 11369 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 11370 * Features to Notifications</a> for more information. 11371 * @param bridgeTag the bridge tag of the notification. 11372 * @return this object for method chaining 11373 */ setBridgeTag(String bridgeTag)11374 public WearableExtender setBridgeTag(String bridgeTag) { 11375 mBridgeTag = bridgeTag; 11376 return this; 11377 } 11378 11379 /** 11380 * Returns the bridge tag of the notification. 11381 * @return the bridge tag or null if not present. 11382 */ getBridgeTag()11383 public String getBridgeTag() { 11384 return mBridgeTag; 11385 } 11386 setFlag(int mask, boolean value)11387 private void setFlag(int mask, boolean value) { 11388 if (value) { 11389 mFlags |= mask; 11390 } else { 11391 mFlags &= ~mask; 11392 } 11393 } 11394 } 11395 11396 /** 11397 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 11398 * with car extensions: 11399 * 11400 * <ol> 11401 * <li>Create an {@link Notification.Builder}, setting any desired 11402 * properties. 11403 * <li>Create a {@link CarExtender}. 11404 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 11405 * {@link CarExtender}. 11406 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 11407 * to apply the extensions to a notification. 11408 * </ol> 11409 * 11410 * <pre class="prettyprint"> 11411 * Notification notification = new Notification.Builder(context) 11412 * ... 11413 * .extend(new CarExtender() 11414 * .set*(...)) 11415 * .build(); 11416 * </pre> 11417 * 11418 * <p>Car extensions can be accessed on an existing notification by using the 11419 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 11420 * to access values. 11421 */ 11422 public static final class CarExtender implements Extender { 11423 private static final String TAG = "CarExtender"; 11424 11425 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 11426 private static final String EXTRA_LARGE_ICON = "large_icon"; 11427 private static final String EXTRA_CONVERSATION = "car_conversation"; 11428 private static final String EXTRA_COLOR = "app_color"; 11429 11430 private Bitmap mLargeIcon; 11431 private UnreadConversation mUnreadConversation; 11432 private int mColor = Notification.COLOR_DEFAULT; 11433 11434 /** 11435 * Create a {@link CarExtender} with default options. 11436 */ CarExtender()11437 public CarExtender() { 11438 } 11439 11440 /** 11441 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 11442 * 11443 * @param notif The notification from which to copy options. 11444 */ CarExtender(Notification notif)11445 public CarExtender(Notification notif) { 11446 Bundle carBundle = notif.extras == null ? 11447 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 11448 if (carBundle != null) { 11449 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON); 11450 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 11451 11452 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 11453 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 11454 } 11455 } 11456 11457 /** 11458 * Apply car extensions to a notification that is being built. This is typically called by 11459 * the {@link Notification.Builder#extend(Notification.Extender)} 11460 * method of {@link Notification.Builder}. 11461 */ 11462 @Override extend(Notification.Builder builder)11463 public Notification.Builder extend(Notification.Builder builder) { 11464 Bundle carExtensions = new Bundle(); 11465 11466 if (mLargeIcon != null) { 11467 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 11468 } 11469 if (mColor != Notification.COLOR_DEFAULT) { 11470 carExtensions.putInt(EXTRA_COLOR, mColor); 11471 } 11472 11473 if (mUnreadConversation != null) { 11474 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 11475 carExtensions.putBundle(EXTRA_CONVERSATION, b); 11476 } 11477 11478 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 11479 return builder; 11480 } 11481 11482 /** 11483 * Sets the accent color to use when Android Auto presents the notification. 11484 * 11485 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 11486 * to accent the displayed notification. However, not all colors are acceptable in an 11487 * automotive setting. This method can be used to override the color provided in the 11488 * notification in such a situation. 11489 */ setColor(@olorInt int color)11490 public CarExtender setColor(@ColorInt int color) { 11491 mColor = color; 11492 return this; 11493 } 11494 11495 /** 11496 * Gets the accent color. 11497 * 11498 * @see #setColor 11499 */ 11500 @ColorInt getColor()11501 public int getColor() { 11502 return mColor; 11503 } 11504 11505 /** 11506 * Sets the large icon of the car notification. 11507 * 11508 * If no large icon is set in the extender, Android Auto will display the icon 11509 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 11510 * 11511 * @param largeIcon The large icon to use in the car notification. 11512 * @return This object for method chaining. 11513 */ setLargeIcon(Bitmap largeIcon)11514 public CarExtender setLargeIcon(Bitmap largeIcon) { 11515 mLargeIcon = largeIcon; 11516 return this; 11517 } 11518 11519 /** 11520 * Gets the large icon used in this car notification, or null if no icon has been set. 11521 * 11522 * @return The large icon for the car notification. 11523 * @see CarExtender#setLargeIcon 11524 */ getLargeIcon()11525 public Bitmap getLargeIcon() { 11526 return mLargeIcon; 11527 } 11528 11529 /** 11530 * Sets the unread conversation in a message notification. 11531 * 11532 * @param unreadConversation The unread part of the conversation this notification conveys. 11533 * @return This object for method chaining. 11534 */ setUnreadConversation(UnreadConversation unreadConversation)11535 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 11536 mUnreadConversation = unreadConversation; 11537 return this; 11538 } 11539 11540 /** 11541 * Returns the unread conversation conveyed by this notification. 11542 * @see #setUnreadConversation(UnreadConversation) 11543 */ getUnreadConversation()11544 public UnreadConversation getUnreadConversation() { 11545 return mUnreadConversation; 11546 } 11547 11548 /** 11549 * A class which holds the unread messages from a conversation. 11550 */ 11551 public static class UnreadConversation { 11552 private static final String KEY_AUTHOR = "author"; 11553 private static final String KEY_TEXT = "text"; 11554 private static final String KEY_MESSAGES = "messages"; 11555 private static final String KEY_REMOTE_INPUT = "remote_input"; 11556 private static final String KEY_ON_REPLY = "on_reply"; 11557 private static final String KEY_ON_READ = "on_read"; 11558 private static final String KEY_PARTICIPANTS = "participants"; 11559 private static final String KEY_TIMESTAMP = "timestamp"; 11560 11561 private final String[] mMessages; 11562 private final RemoteInput mRemoteInput; 11563 private final PendingIntent mReplyPendingIntent; 11564 private final PendingIntent mReadPendingIntent; 11565 private final String[] mParticipants; 11566 private final long mLatestTimestamp; 11567 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11568 UnreadConversation(String[] messages, RemoteInput remoteInput, 11569 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 11570 String[] participants, long latestTimestamp) { 11571 mMessages = messages; 11572 mRemoteInput = remoteInput; 11573 mReadPendingIntent = readPendingIntent; 11574 mReplyPendingIntent = replyPendingIntent; 11575 mParticipants = participants; 11576 mLatestTimestamp = latestTimestamp; 11577 } 11578 11579 /** 11580 * Gets the list of messages conveyed by this notification. 11581 */ getMessages()11582 public String[] getMessages() { 11583 return mMessages; 11584 } 11585 11586 /** 11587 * Gets the remote input that will be used to convey the response to a message list, or 11588 * null if no such remote input exists. 11589 */ getRemoteInput()11590 public RemoteInput getRemoteInput() { 11591 return mRemoteInput; 11592 } 11593 11594 /** 11595 * Gets the pending intent that will be triggered when the user replies to this 11596 * notification. 11597 */ getReplyPendingIntent()11598 public PendingIntent getReplyPendingIntent() { 11599 return mReplyPendingIntent; 11600 } 11601 11602 /** 11603 * Gets the pending intent that Android Auto will send after it reads aloud all messages 11604 * in this object's message list. 11605 */ getReadPendingIntent()11606 public PendingIntent getReadPendingIntent() { 11607 return mReadPendingIntent; 11608 } 11609 11610 /** 11611 * Gets the participants in the conversation. 11612 */ getParticipants()11613 public String[] getParticipants() { 11614 return mParticipants; 11615 } 11616 11617 /** 11618 * Gets the firs participant in the conversation. 11619 */ getParticipant()11620 public String getParticipant() { 11621 return mParticipants.length > 0 ? mParticipants[0] : null; 11622 } 11623 11624 /** 11625 * Gets the timestamp of the conversation. 11626 */ getLatestTimestamp()11627 public long getLatestTimestamp() { 11628 return mLatestTimestamp; 11629 } 11630 getBundleForUnreadConversation()11631 Bundle getBundleForUnreadConversation() { 11632 Bundle b = new Bundle(); 11633 String author = null; 11634 if (mParticipants != null && mParticipants.length > 1) { 11635 author = mParticipants[0]; 11636 } 11637 Parcelable[] messages = new Parcelable[mMessages.length]; 11638 for (int i = 0; i < messages.length; i++) { 11639 Bundle m = new Bundle(); 11640 m.putString(KEY_TEXT, mMessages[i]); 11641 m.putString(KEY_AUTHOR, author); 11642 messages[i] = m; 11643 } 11644 b.putParcelableArray(KEY_MESSAGES, messages); 11645 if (mRemoteInput != null) { 11646 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 11647 } 11648 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 11649 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 11650 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 11651 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 11652 return b; 11653 } 11654 getUnreadConversationFromBundle(Bundle b)11655 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 11656 if (b == null) { 11657 return null; 11658 } 11659 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 11660 String[] messages = null; 11661 if (parcelableMessages != null) { 11662 String[] tmp = new String[parcelableMessages.length]; 11663 boolean success = true; 11664 for (int i = 0; i < tmp.length; i++) { 11665 if (!(parcelableMessages[i] instanceof Bundle)) { 11666 success = false; 11667 break; 11668 } 11669 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 11670 if (tmp[i] == null) { 11671 success = false; 11672 break; 11673 } 11674 } 11675 if (success) { 11676 messages = tmp; 11677 } else { 11678 return null; 11679 } 11680 } 11681 11682 PendingIntent onRead = b.getParcelable(KEY_ON_READ); 11683 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY); 11684 11685 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT); 11686 11687 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 11688 if (participants == null || participants.length != 1) { 11689 return null; 11690 } 11691 11692 return new UnreadConversation(messages, 11693 remoteInput, 11694 onReply, 11695 onRead, 11696 participants, b.getLong(KEY_TIMESTAMP)); 11697 } 11698 }; 11699 11700 /** 11701 * Builder class for {@link CarExtender.UnreadConversation} objects. 11702 */ 11703 public static class Builder { 11704 private final List<String> mMessages = new ArrayList<String>(); 11705 private final String mParticipant; 11706 private RemoteInput mRemoteInput; 11707 private PendingIntent mReadPendingIntent; 11708 private PendingIntent mReplyPendingIntent; 11709 private long mLatestTimestamp; 11710 11711 /** 11712 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 11713 * 11714 * @param name The name of the other participant in the conversation. 11715 */ Builder(String name)11716 public Builder(String name) { 11717 mParticipant = name; 11718 } 11719 11720 /** 11721 * Appends a new unread message to the list of messages for this conversation. 11722 * 11723 * The messages should be added from oldest to newest. 11724 * 11725 * @param message The text of the new unread message. 11726 * @return This object for method chaining. 11727 */ addMessage(String message)11728 public Builder addMessage(String message) { 11729 mMessages.add(message); 11730 return this; 11731 } 11732 11733 /** 11734 * Sets the pending intent and remote input which will convey the reply to this 11735 * notification. 11736 * 11737 * @param pendingIntent The pending intent which will be triggered on a reply. 11738 * @param remoteInput The remote input parcelable which will carry the reply. 11739 * @return This object for method chaining. 11740 * 11741 * @see CarExtender.UnreadConversation#getRemoteInput 11742 * @see CarExtender.UnreadConversation#getReplyPendingIntent 11743 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)11744 public Builder setReplyAction( 11745 PendingIntent pendingIntent, RemoteInput remoteInput) { 11746 mRemoteInput = remoteInput; 11747 mReplyPendingIntent = pendingIntent; 11748 11749 return this; 11750 } 11751 11752 /** 11753 * Sets the pending intent that will be sent once the messages in this notification 11754 * are read. 11755 * 11756 * @param pendingIntent The pending intent to use. 11757 * @return This object for method chaining. 11758 */ setReadPendingIntent(PendingIntent pendingIntent)11759 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 11760 mReadPendingIntent = pendingIntent; 11761 return this; 11762 } 11763 11764 /** 11765 * Sets the timestamp of the most recent message in an unread conversation. 11766 * 11767 * If a messaging notification has been posted by your application and has not 11768 * yet been cancelled, posting a later notification with the same id and tag 11769 * but without a newer timestamp may result in Android Auto not displaying a 11770 * heads up notification for the later notification. 11771 * 11772 * @param timestamp The timestamp of the most recent message in the conversation. 11773 * @return This object for method chaining. 11774 */ setLatestTimestamp(long timestamp)11775 public Builder setLatestTimestamp(long timestamp) { 11776 mLatestTimestamp = timestamp; 11777 return this; 11778 } 11779 11780 /** 11781 * Builds a new unread conversation object. 11782 * 11783 * @return The new unread conversation object. 11784 */ build()11785 public UnreadConversation build() { 11786 String[] messages = mMessages.toArray(new String[mMessages.size()]); 11787 String[] participants = { mParticipant }; 11788 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 11789 mReadPendingIntent, participants, mLatestTimestamp); 11790 } 11791 } 11792 } 11793 11794 /** 11795 * <p>Helper class to add Android TV extensions to notifications. To create a notification 11796 * with a TV extension: 11797 * 11798 * <ol> 11799 * <li>Create an {@link Notification.Builder}, setting any desired properties. 11800 * <li>Create a {@link TvExtender}. 11801 * <li>Set TV-specific properties using the {@code set} methods of 11802 * {@link TvExtender}. 11803 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 11804 * to apply the extension to a notification. 11805 * </ol> 11806 * 11807 * <pre class="prettyprint"> 11808 * Notification notification = new Notification.Builder(context) 11809 * ... 11810 * .extend(new TvExtender() 11811 * .set*(...)) 11812 * .build(); 11813 * </pre> 11814 * 11815 * <p>TV extensions can be accessed on an existing notification by using the 11816 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 11817 * to access values. 11818 * 11819 * @hide 11820 */ 11821 @SystemApi 11822 public static final class TvExtender implements Extender { 11823 private static final String TAG = "TvExtender"; 11824 11825 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 11826 private static final String EXTRA_FLAGS = "flags"; 11827 private static final String EXTRA_CONTENT_INTENT = "content_intent"; 11828 private static final String EXTRA_DELETE_INTENT = "delete_intent"; 11829 private static final String EXTRA_CHANNEL_ID = "channel_id"; 11830 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 11831 11832 // Flags bitwise-ored to mFlags 11833 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 11834 11835 private int mFlags; 11836 private String mChannelId; 11837 private PendingIntent mContentIntent; 11838 private PendingIntent mDeleteIntent; 11839 private boolean mSuppressShowOverApps; 11840 11841 /** 11842 * Create a {@link TvExtender} with default options. 11843 */ TvExtender()11844 public TvExtender() { 11845 mFlags = FLAG_AVAILABLE_ON_TV; 11846 } 11847 11848 /** 11849 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 11850 * 11851 * @param notif The notification from which to copy options. 11852 */ TvExtender(Notification notif)11853 public TvExtender(Notification notif) { 11854 Bundle bundle = notif.extras == null ? 11855 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 11856 if (bundle != null) { 11857 mFlags = bundle.getInt(EXTRA_FLAGS); 11858 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 11859 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 11860 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT); 11861 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT); 11862 } 11863 } 11864 11865 /** 11866 * Apply a TV extension to a notification that is being built. This is typically called by 11867 * the {@link Notification.Builder#extend(Notification.Extender)} 11868 * method of {@link Notification.Builder}. 11869 */ 11870 @Override extend(Notification.Builder builder)11871 public Notification.Builder extend(Notification.Builder builder) { 11872 Bundle bundle = new Bundle(); 11873 11874 bundle.putInt(EXTRA_FLAGS, mFlags); 11875 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 11876 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 11877 if (mContentIntent != null) { 11878 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 11879 } 11880 11881 if (mDeleteIntent != null) { 11882 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 11883 } 11884 11885 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 11886 return builder; 11887 } 11888 11889 /** 11890 * Returns true if this notification should be shown on TV. This method return true 11891 * if the notification was extended with a TvExtender. 11892 */ isAvailableOnTv()11893 public boolean isAvailableOnTv() { 11894 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 11895 } 11896 11897 /** 11898 * Specifies the channel the notification should be delivered on when shown on TV. 11899 * It can be different from the channel that the notification is delivered to when 11900 * posting on a non-TV device. 11901 */ setChannel(String channelId)11902 public TvExtender setChannel(String channelId) { 11903 mChannelId = channelId; 11904 return this; 11905 } 11906 11907 /** 11908 * Specifies the channel the notification should be delivered on when shown on TV. 11909 * It can be different from the channel that the notification is delivered to when 11910 * posting on a non-TV device. 11911 */ setChannelId(String channelId)11912 public TvExtender setChannelId(String channelId) { 11913 mChannelId = channelId; 11914 return this; 11915 } 11916 11917 /** @removed */ 11918 @Deprecated getChannel()11919 public String getChannel() { 11920 return mChannelId; 11921 } 11922 11923 /** 11924 * Returns the id of the channel this notification posts to on TV. 11925 */ getChannelId()11926 public String getChannelId() { 11927 return mChannelId; 11928 } 11929 11930 /** 11931 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 11932 * If provided, it is used instead of the content intent specified 11933 * at the level of Notification. 11934 */ setContentIntent(PendingIntent intent)11935 public TvExtender setContentIntent(PendingIntent intent) { 11936 mContentIntent = intent; 11937 return this; 11938 } 11939 11940 /** 11941 * Returns the TV-specific content intent. If this method returns null, the 11942 * main content intent on the notification should be used. 11943 * 11944 * @see {@link Notification#contentIntent} 11945 */ getContentIntent()11946 public PendingIntent getContentIntent() { 11947 return mContentIntent; 11948 } 11949 11950 /** 11951 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 11952 * by the user on TV. If provided, it is used instead of the delete intent specified 11953 * at the level of Notification. 11954 */ setDeleteIntent(PendingIntent intent)11955 public TvExtender setDeleteIntent(PendingIntent intent) { 11956 mDeleteIntent = intent; 11957 return this; 11958 } 11959 11960 /** 11961 * Returns the TV-specific delete intent. If this method returns null, the 11962 * main delete intent on the notification should be used. 11963 * 11964 * @see {@link Notification#deleteIntent} 11965 */ getDeleteIntent()11966 public PendingIntent getDeleteIntent() { 11967 return mDeleteIntent; 11968 } 11969 11970 /** 11971 * Specifies whether this notification should suppress showing a message over top of apps 11972 * outside of the launcher. 11973 */ setSuppressShowOverApps(boolean suppress)11974 public TvExtender setSuppressShowOverApps(boolean suppress) { 11975 mSuppressShowOverApps = suppress; 11976 return this; 11977 } 11978 11979 /** 11980 * Returns true if this notification should not show messages over top of apps 11981 * outside of the launcher. 11982 */ getSuppressShowOverApps()11983 public boolean getSuppressShowOverApps() { 11984 return mSuppressShowOverApps; 11985 } 11986 } 11987 11988 /** 11989 * Get an array of Parcelable objects from a parcelable array bundle field. 11990 * Update the bundle to have a typed array so fetches in the future don't need 11991 * to do an array copy. 11992 */ 11993 @Nullable getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)11994 private static <T extends Parcelable> T[] getParcelableArrayFromBundle( 11995 Bundle bundle, String key, Class<T> itemClass) { 11996 final Parcelable[] array = bundle.getParcelableArray(key); 11997 final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); 11998 if (arrayClass.isInstance(array) || array == null) { 11999 return (T[]) array; 12000 } 12001 final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); 12002 for (int i = 0; i < array.length; i++) { 12003 typedArray[i] = (T) array[i]; 12004 } 12005 bundle.putParcelableArray(key, typedArray); 12006 return typedArray; 12007 } 12008 12009 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)12010 public BuilderRemoteViews(Parcel parcel) { 12011 super(parcel); 12012 } 12013 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)12014 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 12015 super(appInfo, layoutId); 12016 } 12017 12018 @Override clone()12019 public BuilderRemoteViews clone() { 12020 Parcel p = Parcel.obtain(); 12021 writeToParcel(p, 0); 12022 p.setDataPosition(0); 12023 BuilderRemoteViews brv = new BuilderRemoteViews(p); 12024 p.recycle(); 12025 return brv; 12026 } 12027 12028 /** 12029 * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden. 12030 * 12031 * @see RemoteViews#shouldUseStaticFilter() 12032 */ 12033 @Override shouldUseStaticFilter()12034 protected boolean shouldUseStaticFilter() { 12035 return true; 12036 } 12037 } 12038 12039 /** 12040 * A result object where information about the template that was created is saved. 12041 */ 12042 private static class TemplateBindResult { 12043 boolean mRightIconVisible; 12044 float mRightIconWidthDp; 12045 float mRightIconHeightDp; 12046 12047 /** 12048 * The margin end that needs to be added to the heading so that it won't overlap 12049 * with the large icon. This value includes the space required to accommodate the large 12050 * icon, but should be added to the space needed to accommodate the expander. This does 12051 * not include the 16dp content margin that all notification views must have. 12052 */ 12053 public final MarginSet mHeadingExtraMarginSet = new MarginSet(); 12054 12055 /** 12056 * The margin end that needs to be added to the heading so that it won't overlap 12057 * with the large icon. This value includes the space required to accommodate the large 12058 * icon as well as the expander. This does not include the 16dp content margin that all 12059 * notification views must have. 12060 */ 12061 public final MarginSet mHeadingFullMarginSet = new MarginSet(); 12062 12063 /** 12064 * The margin end that needs to be added to the title text of the big state 12065 * so that it won't overlap with the large icon, but assuming the text can run under 12066 * the expander when that icon is not visible. 12067 */ 12068 public final MarginSet mTitleMarginSet = new MarginSet(); 12069 setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12070 public void setRightIconState(boolean visible, float widthDp, float heightDp, 12071 float marginEndDpIfVisible, float expanderSizeDp) { 12072 mRightIconVisible = visible; 12073 mRightIconWidthDp = widthDp; 12074 mRightIconHeightDp = heightDp; 12075 mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible); 12076 mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp); 12077 mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp); 12078 } 12079 12080 /** 12081 * This contains the end margins for a view when the right icon is visible or not. These 12082 * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the 12083 * left_icon and adjust the margins, and to undo that change as well. 12084 */ 12085 private class MarginSet { 12086 private float mValueIfGone; 12087 private float mValueIfVisible; 12088 setValues(float valueIfGone, float valueIfVisible)12089 public void setValues(float valueIfGone, float valueIfVisible) { 12090 mValueIfGone = valueIfGone; 12091 mValueIfVisible = valueIfVisible; 12092 } 12093 applyToView(@onNull RemoteViews views, @IdRes int viewId)12094 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) { 12095 applyToView(views, viewId, 0); 12096 } 12097 applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12098 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId, 12099 float extraMarginDp) { 12100 final float marginEndDp = getDpValue() + extraMarginDp; 12101 if (viewId == R.id.notification_header) { 12102 views.setFloat(R.id.notification_header, 12103 "setTopLineExtraMarginEndDp", marginEndDp); 12104 } else if (viewId == R.id.text || viewId == R.id.big_text) { 12105 if (mValueIfGone != 0) { 12106 throw new RuntimeException("Programming error: `text` and `big_text` use " 12107 + "ImageFloatingTextView which can either show a margin or not; " 12108 + "thus mValueIfGone must be 0, but it was " + mValueIfGone); 12109 } 12110 // Note that the caller must set "setNumIndentLines" to a positive int in order 12111 // for this margin to do anything at all. 12112 views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible); 12113 views.setBoolean(viewId, "setHasImage", mRightIconVisible); 12114 // Apply just the *extra* margin as the view layout margin; this will be 12115 // unchanged depending on the visibility of the image, but it means that the 12116 // extra margin applies to *every* line of text instead of just indented lines. 12117 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12118 extraMarginDp, TypedValue.COMPLEX_UNIT_DIP); 12119 } else { 12120 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12121 marginEndDp, TypedValue.COMPLEX_UNIT_DIP); 12122 } 12123 if (mRightIconVisible) { 12124 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, 12125 TypedValue.createComplexDimension( 12126 mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12127 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone, 12128 TypedValue.createComplexDimension( 12129 mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12130 } 12131 } 12132 getDpValue()12133 public float getDpValue() { 12134 return mRightIconVisible ? mValueIfVisible : mValueIfGone; 12135 } 12136 } 12137 } 12138 12139 private static class StandardTemplateParams { 12140 /** 12141 * Notifications will be minimally decorated with ONLY an icon and expander: 12142 * <li>A large icon is never shown. 12143 * <li>A progress bar is never shown. 12144 * <li>The expanded and heads up states do not show actions, even if provided. 12145 */ 12146 public static final int DECORATION_MINIMAL = 1; 12147 12148 /** 12149 * Notifications will be partially decorated with AT LEAST an icon and expander: 12150 * <li>A large icon is shown if provided. 12151 * <li>A progress bar is shown if provided and enough space remains below the content. 12152 * <li>Actions are shown in the expanded and heads up states. 12153 */ 12154 public static final int DECORATION_PARTIAL = 2; 12155 12156 public static int VIEW_TYPE_UNSPECIFIED = 0; 12157 public static int VIEW_TYPE_NORMAL = 1; 12158 public static int VIEW_TYPE_BIG = 2; 12159 public static int VIEW_TYPE_HEADS_UP = 3; 12160 public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state 12161 public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version 12162 public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group 12163 12164 int mViewType = VIEW_TYPE_UNSPECIFIED; 12165 boolean mHeaderless; 12166 boolean mHideAppName; 12167 boolean mHideTitle; 12168 boolean mHideSubText; 12169 boolean mHideTime; 12170 boolean mHideActions; 12171 boolean mHideProgress; 12172 boolean mHideSnoozeButton; 12173 boolean mHideLeftIcon; 12174 boolean mHideRightIcon; 12175 Icon mPromotedPicture; 12176 boolean mCallStyleActions; 12177 boolean mAllowTextWithProgress; 12178 int mTitleViewId; 12179 int mTextViewId; 12180 CharSequence title; 12181 CharSequence text; 12182 CharSequence headerTextSecondary; 12183 CharSequence summaryText; 12184 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12185 boolean allowColorization = true; 12186 boolean mHighlightExpander = false; 12187 reset()12188 final StandardTemplateParams reset() { 12189 mViewType = VIEW_TYPE_UNSPECIFIED; 12190 mHeaderless = false; 12191 mHideAppName = false; 12192 mHideTitle = false; 12193 mHideSubText = false; 12194 mHideTime = false; 12195 mHideActions = false; 12196 mHideProgress = false; 12197 mHideSnoozeButton = false; 12198 mHideLeftIcon = false; 12199 mHideRightIcon = false; 12200 mPromotedPicture = null; 12201 mCallStyleActions = false; 12202 mAllowTextWithProgress = false; 12203 mTitleViewId = R.id.title; 12204 mTextViewId = R.id.text; 12205 title = null; 12206 text = null; 12207 summaryText = null; 12208 headerTextSecondary = null; 12209 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12210 allowColorization = true; 12211 mHighlightExpander = false; 12212 return this; 12213 } 12214 hasTitle()12215 final boolean hasTitle() { 12216 return !TextUtils.isEmpty(title) && !mHideTitle; 12217 } 12218 viewType(int viewType)12219 final StandardTemplateParams viewType(int viewType) { 12220 mViewType = viewType; 12221 return this; 12222 } 12223 headerless(boolean headerless)12224 public StandardTemplateParams headerless(boolean headerless) { 12225 mHeaderless = headerless; 12226 return this; 12227 } 12228 hideAppName(boolean hideAppName)12229 public StandardTemplateParams hideAppName(boolean hideAppName) { 12230 mHideAppName = hideAppName; 12231 return this; 12232 } 12233 hideSubText(boolean hideSubText)12234 public StandardTemplateParams hideSubText(boolean hideSubText) { 12235 mHideSubText = hideSubText; 12236 return this; 12237 } 12238 hideTime(boolean hideTime)12239 public StandardTemplateParams hideTime(boolean hideTime) { 12240 mHideTime = hideTime; 12241 return this; 12242 } 12243 hideActions(boolean hideActions)12244 final StandardTemplateParams hideActions(boolean hideActions) { 12245 this.mHideActions = hideActions; 12246 return this; 12247 } 12248 hideProgress(boolean hideProgress)12249 final StandardTemplateParams hideProgress(boolean hideProgress) { 12250 this.mHideProgress = hideProgress; 12251 return this; 12252 } 12253 hideTitle(boolean hideTitle)12254 final StandardTemplateParams hideTitle(boolean hideTitle) { 12255 this.mHideTitle = hideTitle; 12256 return this; 12257 } 12258 callStyleActions(boolean callStyleActions)12259 final StandardTemplateParams callStyleActions(boolean callStyleActions) { 12260 this.mCallStyleActions = callStyleActions; 12261 return this; 12262 } 12263 allowTextWithProgress(boolean allowTextWithProgress)12264 final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) { 12265 this.mAllowTextWithProgress = allowTextWithProgress; 12266 return this; 12267 } 12268 hideSnoozeButton(boolean hideSnoozeButton)12269 final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) { 12270 this.mHideSnoozeButton = hideSnoozeButton; 12271 return this; 12272 } 12273 promotedPicture(Icon promotedPicture)12274 final StandardTemplateParams promotedPicture(Icon promotedPicture) { 12275 this.mPromotedPicture = promotedPicture; 12276 return this; 12277 } 12278 titleViewId(int titleViewId)12279 public StandardTemplateParams titleViewId(int titleViewId) { 12280 mTitleViewId = titleViewId; 12281 return this; 12282 } 12283 textViewId(int textViewId)12284 public StandardTemplateParams textViewId(int textViewId) { 12285 mTextViewId = textViewId; 12286 return this; 12287 } 12288 title(CharSequence title)12289 final StandardTemplateParams title(CharSequence title) { 12290 this.title = title; 12291 return this; 12292 } 12293 text(CharSequence text)12294 final StandardTemplateParams text(CharSequence text) { 12295 this.text = text; 12296 return this; 12297 } 12298 summaryText(CharSequence text)12299 final StandardTemplateParams summaryText(CharSequence text) { 12300 this.summaryText = text; 12301 return this; 12302 } 12303 headerTextSecondary(CharSequence text)12304 final StandardTemplateParams headerTextSecondary(CharSequence text) { 12305 this.headerTextSecondary = text; 12306 return this; 12307 } 12308 12309 hideLeftIcon(boolean hideLeftIcon)12310 final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) { 12311 this.mHideLeftIcon = hideLeftIcon; 12312 return this; 12313 } 12314 hideRightIcon(boolean hideRightIcon)12315 final StandardTemplateParams hideRightIcon(boolean hideRightIcon) { 12316 this.mHideRightIcon = hideRightIcon; 12317 return this; 12318 } 12319 disallowColorization()12320 final StandardTemplateParams disallowColorization() { 12321 this.allowColorization = false; 12322 return this; 12323 } 12324 highlightExpander(boolean highlight)12325 final StandardTemplateParams highlightExpander(boolean highlight) { 12326 this.mHighlightExpander = highlight; 12327 return this; 12328 } 12329 fillTextsFrom(Builder b)12330 final StandardTemplateParams fillTextsFrom(Builder b) { 12331 Bundle extras = b.mN.extras; 12332 this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 12333 this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 12334 this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT); 12335 return this; 12336 } 12337 12338 /** 12339 * Set the maximum lines of remote input history lines allowed. 12340 * @param maxRemoteInputHistory The number of lines. 12341 * @return The builder for method chaining. 12342 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)12343 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 12344 this.maxRemoteInputHistory = maxRemoteInputHistory; 12345 return this; 12346 } 12347 decorationType(int decorationType)12348 public StandardTemplateParams decorationType(int decorationType) { 12349 hideTitle(true); 12350 // Minimally decorated custom views do not show certain pieces of chrome that have 12351 // always been shown when using DecoratedCustomViewStyle. 12352 boolean hideOtherFields = decorationType <= DECORATION_MINIMAL; 12353 hideLeftIcon(false); // The left icon decoration is better than showing nothing. 12354 hideRightIcon(hideOtherFields); 12355 hideProgress(hideOtherFields); 12356 hideActions(hideOtherFields); 12357 return this; 12358 } 12359 } 12360 12361 /** 12362 * A utility which stores and calculates the palette of colors used to color notifications. 12363 * @hide 12364 */ 12365 @VisibleForTesting 12366 public static class Colors { 12367 private int mPaletteIsForRawColor = COLOR_INVALID; 12368 private boolean mPaletteIsForColorized = false; 12369 private boolean mPaletteIsForNightMode = false; 12370 // The following colors are the palette 12371 private int mBackgroundColor = COLOR_INVALID; 12372 private int mProtectionColor = COLOR_INVALID; 12373 private int mPrimaryTextColor = COLOR_INVALID; 12374 private int mSecondaryTextColor = COLOR_INVALID; 12375 private int mPrimaryAccentColor = COLOR_INVALID; 12376 private int mSecondaryAccentColor = COLOR_INVALID; 12377 private int mTertiaryAccentColor = COLOR_INVALID; 12378 private int mOnAccentTextColor = COLOR_INVALID; 12379 private int mErrorColor = COLOR_INVALID; 12380 private int mContrastColor = COLOR_INVALID; 12381 private int mRippleAlpha = 0x33; 12382 12383 /** 12384 * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which 12385 * returns null when the context is a mock with no theme. 12386 * 12387 * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper 12388 * instances can allocate as much as 5MB of memory, so its important to call this method 12389 * only when necessary, getting as many attributes as possible from each call. 12390 * 12391 * @see Resources.Theme#obtainStyledAttributes(int[]) 12392 */ 12393 @Nullable obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12394 private static TypedArray obtainDayNightAttributes(@NonNull Context ctx, 12395 @NonNull @StyleableRes int[] attrs) { 12396 // when testing, the mock context may have no theme 12397 if (ctx.getTheme() == null) { 12398 return null; 12399 } 12400 Resources.Theme theme = new ContextThemeWrapper(ctx, 12401 R.style.Theme_DeviceDefault_DayNight).getTheme(); 12402 return theme.obtainStyledAttributes(attrs); 12403 } 12404 12405 /** A null-safe wrapper of TypedArray.getColor because mocks return null */ getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12406 private static @ColorInt int getColor(@Nullable TypedArray ta, int index, 12407 @ColorInt int defValue) { 12408 return ta == null ? defValue : ta.getColor(index, defValue); 12409 } 12410 12411 /** 12412 * Resolve the palette. If the inputs have not changed, this will be a no-op. 12413 * This does not handle invalidating the resolved colors when the context itself changes, 12414 * because that case does not happen in the current notification inflation pipeline; we will 12415 * recreate a new builder (and thus a new palette) when reinflating notifications for a new 12416 * theme (admittedly, we do the same for night mode, but that's easy to check). 12417 * 12418 * @param ctx the builder context. 12419 * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha. 12420 * @param isColorized whether the notification is colorized. 12421 * @param nightMode whether the UI is in night mode. 12422 */ resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12423 public void resolvePalette(Context ctx, int rawColor, 12424 boolean isColorized, boolean nightMode) { 12425 if (mPaletteIsForRawColor == rawColor 12426 && mPaletteIsForColorized == isColorized 12427 && mPaletteIsForNightMode == nightMode) { 12428 return; 12429 } 12430 mPaletteIsForRawColor = rawColor; 12431 mPaletteIsForColorized = isColorized; 12432 mPaletteIsForNightMode = nightMode; 12433 12434 if (isColorized) { 12435 if (rawColor == COLOR_DEFAULT) { 12436 int[] attrs = {R.attr.colorAccentSecondary}; 12437 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12438 mBackgroundColor = getColor(ta, 0, Color.WHITE); 12439 } 12440 } else { 12441 mBackgroundColor = rawColor; 12442 } 12443 mProtectionColor = COLOR_INVALID; // filled in at the end 12444 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12445 ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode), 12446 mBackgroundColor, 4.5); 12447 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12448 ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode), 12449 mBackgroundColor, 4.5); 12450 mContrastColor = mPrimaryTextColor; 12451 mPrimaryAccentColor = mPrimaryTextColor; 12452 mSecondaryAccentColor = mSecondaryTextColor; 12453 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); 12454 mOnAccentTextColor = mBackgroundColor; 12455 mErrorColor = mPrimaryTextColor; 12456 mRippleAlpha = 0x33; 12457 } else { 12458 int[] attrs = { 12459 R.attr.colorSurface, 12460 R.attr.colorBackgroundFloating, 12461 R.attr.textColorPrimary, 12462 R.attr.textColorSecondary, 12463 R.attr.colorAccent, 12464 R.attr.colorAccentSecondary, 12465 R.attr.colorAccentTertiary, 12466 R.attr.textColorOnAccent, 12467 R.attr.colorError, 12468 R.attr.colorControlHighlight 12469 }; 12470 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12471 mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE); 12472 mProtectionColor = getColor(ta, 1, COLOR_INVALID); 12473 mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID); 12474 mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID); 12475 mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID); 12476 mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID); 12477 mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID); 12478 mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID); 12479 mErrorColor = getColor(ta, 8, COLOR_INVALID); 12480 mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff)); 12481 } 12482 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor, 12483 mBackgroundColor, nightMode); 12484 12485 // make sure every color has a valid value 12486 if (mPrimaryTextColor == COLOR_INVALID) { 12487 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor( 12488 ctx, mBackgroundColor, nightMode); 12489 } 12490 if (mSecondaryTextColor == COLOR_INVALID) { 12491 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor( 12492 ctx, mBackgroundColor, nightMode); 12493 } 12494 if (mPrimaryAccentColor == COLOR_INVALID) { 12495 mPrimaryAccentColor = mContrastColor; 12496 } 12497 if (mSecondaryAccentColor == COLOR_INVALID) { 12498 mSecondaryAccentColor = mContrastColor; 12499 } 12500 if (mTertiaryAccentColor == COLOR_INVALID) { 12501 mTertiaryAccentColor = mContrastColor; 12502 } 12503 if (mOnAccentTextColor == COLOR_INVALID) { 12504 mOnAccentTextColor = ColorUtils.setAlphaComponent( 12505 ContrastColorUtil.resolvePrimaryColor( 12506 ctx, mTertiaryAccentColor, nightMode), 0xFF); 12507 } 12508 if (mErrorColor == COLOR_INVALID) { 12509 mErrorColor = mPrimaryTextColor; 12510 } 12511 } 12512 // make sure every color has a valid value 12513 if (mProtectionColor == COLOR_INVALID) { 12514 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f); 12515 } 12516 } 12517 12518 /** calculates the contrast color for the non-colorized notifications */ calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12519 private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor, 12520 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) { 12521 int color; 12522 if (rawColor == COLOR_DEFAULT) { 12523 color = accentColor; 12524 if (color == COLOR_INVALID) { 12525 color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode); 12526 } 12527 } else { 12528 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor, 12529 nightMode); 12530 } 12531 return flattenAlpha(color, backgroundColor); 12532 } 12533 12534 /** remove any alpha by manually blending it with the given background. */ flattenAlpha(@olorInt int color, @ColorInt int background)12535 private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) { 12536 return Color.alpha(color) == 0xff ? color 12537 : ContrastColorUtil.compositeColors(color, background); 12538 } 12539 12540 /** @return the notification's background color */ getBackgroundColor()12541 public @ColorInt int getBackgroundColor() { 12542 return mBackgroundColor; 12543 } 12544 12545 /** 12546 * @return the "surface protection" color from the theme, 12547 * or a variant of the normal background color when colorized. 12548 */ getProtectionColor()12549 public @ColorInt int getProtectionColor() { 12550 return mProtectionColor; 12551 } 12552 12553 /** @return the color for the most prominent text */ getPrimaryTextColor()12554 public @ColorInt int getPrimaryTextColor() { 12555 return mPrimaryTextColor; 12556 } 12557 12558 /** @return the color for less prominent text */ getSecondaryTextColor()12559 public @ColorInt int getSecondaryTextColor() { 12560 return mSecondaryTextColor; 12561 } 12562 12563 /** @return the theme's accent color for colored UI elements. */ getPrimaryAccentColor()12564 public @ColorInt int getPrimaryAccentColor() { 12565 return mPrimaryAccentColor; 12566 } 12567 12568 /** @return the theme's secondary accent color for colored UI elements. */ getSecondaryAccentColor()12569 public @ColorInt int getSecondaryAccentColor() { 12570 return mSecondaryAccentColor; 12571 } 12572 12573 /** @return the theme's tertiary accent color for colored UI elements. */ getTertiaryAccentColor()12574 public @ColorInt int getTertiaryAccentColor() { 12575 return mTertiaryAccentColor; 12576 } 12577 12578 /** @return the theme's text color to be used on the tertiary accent color. */ getOnAccentTextColor()12579 public @ColorInt int getOnAccentTextColor() { 12580 return mOnAccentTextColor; 12581 } 12582 12583 /** 12584 * @return the contrast-adjusted version of the color provided by the app, or the 12585 * primary text color when colorized. 12586 */ getContrastColor()12587 public @ColorInt int getContrastColor() { 12588 return mContrastColor; 12589 } 12590 12591 /** @return the theme's error color, or the primary text color when colorized. */ getErrorColor()12592 public @ColorInt int getErrorColor() { 12593 return mErrorColor; 12594 } 12595 12596 /** @return the alpha component of the current theme's control highlight color. */ getRippleAlpha()12597 public int getRippleAlpha() { 12598 return mRippleAlpha; 12599 } 12600 } 12601 } 12602