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.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; 21 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; 22 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; 23 import static android.app.admin.DevicePolicyResources.UNDEFINED; 24 import static android.graphics.drawable.Icon.TYPE_URI; 25 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; 26 27 import static java.util.Objects.requireNonNull; 28 29 import android.annotation.ColorInt; 30 import android.annotation.ColorRes; 31 import android.annotation.DimenRes; 32 import android.annotation.Dimension; 33 import android.annotation.DrawableRes; 34 import android.annotation.IdRes; 35 import android.annotation.IntDef; 36 import android.annotation.NonNull; 37 import android.annotation.Nullable; 38 import android.annotation.RequiresPermission; 39 import android.annotation.SdkConstant; 40 import android.annotation.SdkConstant.SdkConstantType; 41 import android.annotation.StringRes; 42 import android.annotation.StyleableRes; 43 import android.annotation.SuppressLint; 44 import android.annotation.SystemApi; 45 import android.annotation.TestApi; 46 import android.app.admin.DevicePolicyManager; 47 import android.compat.annotation.UnsupportedAppUsage; 48 import android.content.Context; 49 import android.content.Intent; 50 import android.content.LocusId; 51 import android.content.pm.ApplicationInfo; 52 import android.content.pm.PackageManager; 53 import android.content.pm.PackageManager.NameNotFoundException; 54 import android.content.pm.ShortcutInfo; 55 import android.content.res.ColorStateList; 56 import android.content.res.Configuration; 57 import android.content.res.Resources; 58 import android.content.res.TypedArray; 59 import android.graphics.Bitmap; 60 import android.graphics.Canvas; 61 import android.graphics.Color; 62 import android.graphics.PorterDuff; 63 import android.graphics.drawable.Drawable; 64 import android.graphics.drawable.Icon; 65 import android.media.AudioAttributes; 66 import android.media.AudioManager; 67 import android.media.PlayerBase; 68 import android.media.session.MediaSession; 69 import android.net.Uri; 70 import android.os.BadParcelableException; 71 import android.os.Build; 72 import android.os.Bundle; 73 import android.os.IBinder; 74 import android.os.Parcel; 75 import android.os.Parcelable; 76 import android.os.SystemClock; 77 import android.os.SystemProperties; 78 import android.os.UserHandle; 79 import android.os.UserManager; 80 import android.provider.Settings; 81 import android.text.BidiFormatter; 82 import android.text.SpannableStringBuilder; 83 import android.text.Spanned; 84 import android.text.TextUtils; 85 import android.text.style.AbsoluteSizeSpan; 86 import android.text.style.CharacterStyle; 87 import android.text.style.ForegroundColorSpan; 88 import android.text.style.RelativeSizeSpan; 89 import android.text.style.TextAppearanceSpan; 90 import android.util.ArraySet; 91 import android.util.Log; 92 import android.util.Pair; 93 import android.util.SparseArray; 94 import android.util.TypedValue; 95 import android.util.proto.ProtoOutputStream; 96 import android.view.ContextThemeWrapper; 97 import android.view.Gravity; 98 import android.view.View; 99 import android.view.contentcapture.ContentCaptureContext; 100 import android.widget.ProgressBar; 101 import android.widget.RemoteViews; 102 103 import com.android.internal.R; 104 import com.android.internal.annotations.VisibleForTesting; 105 import com.android.internal.graphics.ColorUtils; 106 import com.android.internal.util.ArrayUtils; 107 import com.android.internal.util.ContrastColorUtil; 108 109 import java.lang.annotation.Retention; 110 import java.lang.annotation.RetentionPolicy; 111 import java.lang.reflect.Array; 112 import java.lang.reflect.Constructor; 113 import java.util.ArrayList; 114 import java.util.Arrays; 115 import java.util.Collections; 116 import java.util.List; 117 import java.util.Objects; 118 import java.util.Set; 119 import java.util.function.Consumer; 120 121 /** 122 * A class that represents how a persistent notification is to be presented to 123 * the user using the {@link android.app.NotificationManager}. 124 * 125 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 126 * easier to construct Notifications.</p> 127 * 128 * <div class="special reference"> 129 * <h3>Developer Guides</h3> 130 * <p>For a guide to creating notifications, read the 131 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 132 * developer guide.</p> 133 * </div> 134 */ 135 public class Notification implements Parcelable 136 { 137 private static final String TAG = "Notification"; 138 139 /** 140 * @hide 141 */ 142 @Retention(RetentionPolicy.SOURCE) 143 @IntDef({ 144 FOREGROUND_SERVICE_DEFAULT, 145 FOREGROUND_SERVICE_IMMEDIATE, 146 FOREGROUND_SERVICE_DEFERRED 147 }) 148 public @interface ServiceNotificationPolicy {}; 149 150 /** 151 * If the Notification associated with starting a foreground service has been 152 * built using setForegroundServiceBehavior() with this behavior, display of 153 * the notification will usually be suppressed for a short time to avoid visual 154 * disturbances to the user. 155 * @see Notification.Builder#setForegroundServiceBehavior(int) 156 * @see #FOREGROUND_SERVICE_IMMEDIATE 157 * @see #FOREGROUND_SERVICE_DEFERRED 158 */ 159 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0; 160 161 /** 162 * If the Notification associated with starting a foreground service has been 163 * built using setForegroundServiceBehavior() with this behavior, display of 164 * the notification will be immediate even if the default behavior would be 165 * to defer visibility for a short time. 166 * @see Notification.Builder#setForegroundServiceBehavior(int) 167 * @see #FOREGROUND_SERVICE_DEFAULT 168 * @see #FOREGROUND_SERVICE_DEFERRED 169 */ 170 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1; 171 172 /** 173 * If the Notification associated with starting a foreground service has been 174 * built using setForegroundServiceBehavior() with this behavior, display of 175 * the notification will usually be suppressed for a short time to avoid visual 176 * disturbances to the user. 177 * @see Notification.Builder#setForegroundServiceBehavior(int) 178 * @see #FOREGROUND_SERVICE_DEFAULT 179 * @see #FOREGROUND_SERVICE_IMMEDIATE 180 */ 181 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2; 182 183 @ServiceNotificationPolicy 184 private int mFgsDeferBehavior; 185 186 /** 187 * An activity that provides a user interface for adjusting notification preferences for its 188 * containing application. 189 */ 190 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 191 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES 192 = "android.intent.category.NOTIFICATION_PREFERENCES"; 193 194 /** 195 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 196 * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down 197 * what settings should be shown in the target app. 198 */ 199 public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; 200 201 /** 202 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 203 * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down 204 * what settings should be shown in the target app. 205 */ 206 public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; 207 208 /** 209 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 210 * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)} 211 * that can be used to narrow down what settings should be shown in the target app. 212 */ 213 public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; 214 215 /** 216 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 217 * contain the id provided to {@link NotificationManager#notify(String, int, Notification)} 218 * that can be used to narrow down what settings should be shown in the target app. 219 */ 220 public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; 221 222 /** 223 * Use all default values (where applicable). 224 */ 225 public static final int DEFAULT_ALL = ~0; 226 227 /** 228 * Use the default notification sound. This will ignore any given 229 * {@link #sound}. 230 * 231 * <p> 232 * A notification that is noisy is more likely to be presented as a heads-up notification. 233 * </p> 234 * 235 * @see #defaults 236 */ 237 238 public static final int DEFAULT_SOUND = 1; 239 240 /** 241 * Use the default notification vibrate. This will ignore any given 242 * {@link #vibrate}. Using phone vibration requires the 243 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 244 * 245 * <p> 246 * A notification that vibrates is more likely to be presented as a heads-up notification. 247 * </p> 248 * 249 * @see #defaults 250 */ 251 252 public static final int DEFAULT_VIBRATE = 2; 253 254 /** 255 * Use the default notification lights. This will ignore the 256 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 257 * {@link #ledOnMS}. 258 * 259 * @see #defaults 260 */ 261 262 public static final int DEFAULT_LIGHTS = 4; 263 264 /** 265 * Maximum length of CharSequences accepted by Builder and friends. 266 * 267 * <p> 268 * Avoids spamming the system with overly large strings such as full e-mails. 269 */ 270 private static final int MAX_CHARSEQUENCE_LENGTH = 1024; 271 272 /** 273 * Maximum entries of reply text that are accepted by Builder and friends. 274 */ 275 private static final int MAX_REPLY_HISTORY = 5; 276 277 /** 278 * Maximum aspect ratio of the large icon. 16:9 279 */ 280 private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f; 281 282 /** 283 * Maximum number of (generic) action buttons in a notification (contextual action buttons are 284 * handled separately). 285 * @hide 286 */ 287 public static final int MAX_ACTION_BUTTONS = 3; 288 289 /** 290 * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, 291 * we're adding the draft as a String extra to the {@link #contentIntent} using this key. 292 * 293 * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually 294 * sends messages.</p> 295 */ 296 public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; 297 298 /** 299 * A timestamp related to this notification, in milliseconds since the epoch. 300 * 301 * Default value: {@link System#currentTimeMillis() Now}. 302 * 303 * Choose a timestamp that will be most relevant to the user. For most finite events, this 304 * corresponds to the time the event happened (or will happen, in the case of events that have 305 * yet to occur but about which the user is being informed). Indefinite events should be 306 * timestamped according to when the activity began. 307 * 308 * Some examples: 309 * 310 * <ul> 311 * <li>Notification of a new chat message should be stamped when the message was received.</li> 312 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 313 * <li>Notification of a completed file download should be stamped when the download finished.</li> 314 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 315 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 316 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 317 * </ul> 318 * 319 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown 320 * anymore by default and must be opted into by using 321 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 322 */ 323 public long when; 324 325 /** 326 * The creation time of the notification 327 */ 328 private long creationTime; 329 330 /** 331 * The resource id of a drawable to use as the icon in the status bar. 332 * 333 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead. 334 */ 335 @Deprecated 336 @DrawableRes 337 public int icon; 338 339 /** 340 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 341 * leave it at its default value of 0. 342 * 343 * @see android.widget.ImageView#setImageLevel 344 * @see android.graphics.drawable.Drawable#setLevel 345 */ 346 public int iconLevel; 347 348 /** 349 * The number of events that this notification represents. For example, in a new mail 350 * notification, this could be the number of unread messages. 351 * 352 * The system may or may not use this field to modify the appearance of the notification. 353 * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a 354 * badge icon in Launchers that support badging. 355 */ 356 public int number = 0; 357 358 /** 359 * The intent to execute when the expanded status entry is clicked. If 360 * this is an activity, it must include the 361 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 362 * that you take care of task management as described in the 363 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 364 * Stack</a> document. In particular, make sure to read the 365 * <a href="{@docRoot}/training/notify-user/navigation">Start 366 * an Activity from a Notification</a> page for the correct ways to launch an application from a 367 * notification. 368 */ 369 public PendingIntent contentIntent; 370 371 /** 372 * The intent to execute when the notification is explicitly dismissed by the user, either with 373 * the "Clear All" button or by swiping it away individually. 374 * 375 * This probably shouldn't be launching an activity since several of those will be sent 376 * at the same time. 377 */ 378 public PendingIntent deleteIntent; 379 380 /** 381 * An intent to launch instead of posting the notification to the status bar. 382 * 383 * <p> 384 * The system UI may choose to display a heads-up notification, instead of 385 * launching this intent, while the user is using the device. 386 * </p> 387 * 388 * @see Notification.Builder#setFullScreenIntent 389 */ 390 public PendingIntent fullScreenIntent; 391 392 /** 393 * Text that summarizes this notification for accessibility services. 394 * 395 * As of the L release, this text is no longer shown on screen, but it is still useful to 396 * accessibility services (where it serves as an audible announcement of the notification's 397 * appearance). 398 * 399 * @see #tickerView 400 */ 401 public CharSequence tickerText; 402 403 /** 404 * Formerly, a view showing the {@link #tickerText}. 405 * 406 * No longer displayed in the status bar as of API 21. 407 */ 408 @Deprecated 409 public RemoteViews tickerView; 410 411 /** 412 * The view that will represent this notification in the notification list (which is pulled 413 * down from the status bar). 414 * 415 * As of N, this field may be null. The notification view is determined by the inputs 416 * to {@link Notification.Builder}; a custom RemoteViews can optionally be 417 * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. 418 */ 419 @Deprecated 420 public RemoteViews contentView; 421 422 /** 423 * A large-format version of {@link #contentView}, giving the Notification an 424 * opportunity to show more detail. The system UI may choose to show this 425 * instead of the normal content view at its discretion. 426 * 427 * As of N, this field may be null. The expanded notification view is determined by the 428 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 429 * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. 430 */ 431 @Deprecated 432 public RemoteViews bigContentView; 433 434 435 /** 436 * A medium-format version of {@link #contentView}, providing the Notification an 437 * opportunity to add action buttons to contentView. At its discretion, the system UI may 438 * choose to show this as a heads-up notification, which will pop up so the user can see 439 * it without leaving their current activity. 440 * 441 * As of N, this field may be null. The heads-up notification view is determined by the 442 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 443 * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. 444 */ 445 @Deprecated 446 public RemoteViews headsUpContentView; 447 448 private boolean mUsesStandardHeader; 449 450 private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>(); 451 static { 452 STANDARD_LAYOUTS.add(R.layout.notification_template_material_base); 453 STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base); 454 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base); 455 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture); 456 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); 457 STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); 458 STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); 459 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging); 460 STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation); 461 STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); 462 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); 463 STANDARD_LAYOUTS.add(R.layout.notification_template_material_call); 464 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call); 465 STANDARD_LAYOUTS.add(R.layout.notification_template_header); 466 } 467 468 /** 469 * A large bitmap to be shown in the notification content area. 470 * 471 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead. 472 */ 473 @Deprecated 474 public Bitmap largeIcon; 475 476 /** 477 * The sound to play. 478 * 479 * <p> 480 * A notification that is noisy is more likely to be presented as a heads-up notification. 481 * </p> 482 * 483 * <p> 484 * To play the default notification sound, see {@link #defaults}. 485 * </p> 486 * @deprecated use {@link NotificationChannel#getSound()}. 487 */ 488 @Deprecated 489 public Uri sound; 490 491 /** 492 * Use this constant as the value for audioStreamType to request that 493 * the default stream type for notifications be used. Currently the 494 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 495 * 496 * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead. 497 */ 498 @Deprecated 499 public static final int STREAM_DEFAULT = -1; 500 501 /** 502 * The audio stream type to use when playing the sound. 503 * Should be one of the STREAM_ constants from 504 * {@link android.media.AudioManager}. 505 * 506 * @deprecated Use {@link #audioAttributes} instead. 507 */ 508 @Deprecated 509 public int audioStreamType = STREAM_DEFAULT; 510 511 /** 512 * The default value of {@link #audioAttributes}. 513 */ 514 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() 515 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 516 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 517 .build(); 518 519 /** 520 * The {@link AudioAttributes audio attributes} to use when playing the sound. 521 * 522 * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead. 523 */ 524 @Deprecated 525 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 526 527 /** 528 * The pattern with which to vibrate. 529 * 530 * <p> 531 * To vibrate the default pattern, see {@link #defaults}. 532 * </p> 533 * 534 * @see android.os.Vibrator#vibrate(long[],int) 535 * @deprecated use {@link NotificationChannel#getVibrationPattern()}. 536 */ 537 @Deprecated 538 public long[] vibrate; 539 540 /** 541 * The color of the led. The hardware will do its best approximation. 542 * 543 * @see #FLAG_SHOW_LIGHTS 544 * @see #flags 545 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 546 */ 547 @ColorInt 548 @Deprecated 549 public int ledARGB; 550 551 /** 552 * The number of milliseconds for the LED to be on while it's flashing. 553 * The hardware will do its best approximation. 554 * 555 * @see #FLAG_SHOW_LIGHTS 556 * @see #flags 557 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 558 */ 559 @Deprecated 560 public int ledOnMS; 561 562 /** 563 * The number of milliseconds for the LED to be off while it's flashing. 564 * The hardware will do its best approximation. 565 * 566 * @see #FLAG_SHOW_LIGHTS 567 * @see #flags 568 * 569 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 570 */ 571 @Deprecated 572 public int ledOffMS; 573 574 /** 575 * Specifies which values should be taken from the defaults. 576 * <p> 577 * To set, OR the desired from {@link #DEFAULT_SOUND}, 578 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 579 * values, use {@link #DEFAULT_ALL}. 580 * </p> 581 * 582 * @deprecated use {@link NotificationChannel#getSound()} and 583 * {@link NotificationChannel#shouldShowLights()} and 584 * {@link NotificationChannel#shouldVibrate()}. 585 */ 586 @Deprecated 587 public int defaults; 588 589 /** 590 * Bit to be bitwise-ored into the {@link #flags} field that should be 591 * set if you want the LED on for this notification. 592 * <ul> 593 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 594 * or 0 for both ledOnMS and ledOffMS.</li> 595 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 596 * <li>To flash the LED, pass the number of milliseconds that it should 597 * be on and off to ledOnMS and ledOffMS.</li> 598 * </ul> 599 * <p> 600 * Since hardware varies, you are not guaranteed that any of the values 601 * you pass are honored exactly. Use the system defaults if possible 602 * because they will be set to values that work on any given hardware. 603 * <p> 604 * The alpha channel must be set for forward compatibility. 605 * 606 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 607 */ 608 @Deprecated 609 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 610 611 /** 612 * Bit to be bitwise-ored into the {@link #flags} field that should be 613 * set if this notification is in reference to something that is ongoing, 614 * like a phone call. It should not be set if this notification is in 615 * reference to something that happened at a particular point in time, 616 * like a missed phone call. 617 */ 618 public static final int FLAG_ONGOING_EVENT = 0x00000002; 619 620 /** 621 * Bit to be bitwise-ored into the {@link #flags} field that if set, 622 * the audio will be repeated until the notification is 623 * cancelled or the notification window is opened. 624 */ 625 public static final int FLAG_INSISTENT = 0x00000004; 626 627 /** 628 * Bit to be bitwise-ored into the {@link #flags} field that should be 629 * set if you would only like the sound, vibrate and ticker to be played 630 * if the notification was not already showing. 631 * 632 * Note that using this flag will stop any ongoing alerting behaviour such 633 * as sound, vibration or blinking notification LED. 634 */ 635 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 636 637 /** 638 * Bit to be bitwise-ored into the {@link #flags} field that should be 639 * set if the notification should be canceled when it is clicked by the 640 * user. 641 */ 642 public static final int FLAG_AUTO_CANCEL = 0x00000010; 643 644 /** 645 * Bit to be bitwise-ored into the {@link #flags} field that should be 646 * set if the notification should not be canceled when the user clicks 647 * the Clear all button. 648 */ 649 public static final int FLAG_NO_CLEAR = 0x00000020; 650 651 /** 652 * Bit to be bitwise-ored into the {@link #flags} field that should be 653 * set if this notification represents a currently running service. This 654 * will normally be set for you by {@link Service#startForeground}. 655 */ 656 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 657 658 /** 659 * Obsolete flag indicating high-priority notifications; use the priority field instead. 660 * 661 * @deprecated Use {@link #priority} with a positive value. 662 */ 663 @Deprecated 664 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 665 666 /** 667 * Bit to be bitswise-ored into the {@link #flags} field that should be 668 * set if this notification is relevant to the current device only 669 * and it is not recommended that it bridge to other devices. 670 */ 671 public static final int FLAG_LOCAL_ONLY = 0x00000100; 672 673 /** 674 * Bit to be bitswise-ored into the {@link #flags} field that should be 675 * set if this notification is the group summary for a group of notifications. 676 * Grouped notifications may display in a cluster or stack on devices which 677 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 678 */ 679 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 680 681 /** 682 * Bit to be bitswise-ored into the {@link #flags} field that should be 683 * set if this notification is the group summary for an auto-group of notifications. 684 * 685 * @hide 686 */ 687 @SystemApi 688 public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400; 689 690 /** 691 * @hide 692 */ 693 public static final int FLAG_CAN_COLORIZE = 0x00000800; 694 695 /** 696 * Bit to be bitswised-ored into the {@link #flags} field that should be 697 * set by the system if this notification is showing as a bubble. 698 * 699 * Applications cannot set this flag directly; they should instead call 700 * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to 701 * request that a notification be displayed as a bubble, and then check 702 * this flag to see whether that request was honored by the system. 703 */ 704 public static final int FLAG_BUBBLE = 0x00001000; 705 706 /** 707 * Bit to be bitswised-ored into the {@link #flags} field that should be 708 * set by the system if this notification is not dismissible. 709 * 710 * This flag is for internal use only; applications cannot set this flag directly. 711 * @hide 712 */ 713 public static final int FLAG_NO_DISMISS = 0x00002000; 714 715 /** 716 * Bit to be bitwise-ORed into the {@link #flags} field that should be 717 * set by the system if the app that sent this notification does not have the permission to send 718 * full screen intents. 719 * 720 * This flag is for internal use only; applications cannot set this flag directly. 721 * @hide 722 */ 723 public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000; 724 725 /** 726 * Bit to be bitwise-ored into the {@link #flags} field that should be 727 * set if this notification represents a currently running user-initiated job. 728 * 729 * This flag is for internal use only; applications cannot set this flag directly. 730 * @hide 731 */ 732 @TestApi 733 public static final int FLAG_USER_INITIATED_JOB = 0x00008000; 734 735 private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( 736 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 737 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 738 MessagingStyle.class, CallStyle.class); 739 740 /** @hide */ 741 @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, 742 FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, 743 FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, 744 FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE, 745 FLAG_USER_INITIATED_JOB}) 746 @Retention(RetentionPolicy.SOURCE) 747 public @interface NotificationFlags{}; 748 749 public int flags; 750 751 /** @hide */ 752 @IntDef(prefix = { "PRIORITY_" }, value = { 753 PRIORITY_DEFAULT, 754 PRIORITY_LOW, 755 PRIORITY_MIN, 756 PRIORITY_HIGH, 757 PRIORITY_MAX 758 }) 759 @Retention(RetentionPolicy.SOURCE) 760 public @interface Priority {} 761 762 /** 763 * Default notification {@link #priority}. If your application does not prioritize its own 764 * notifications, use this value for all notifications. 765 * 766 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. 767 */ 768 @Deprecated 769 public static final int PRIORITY_DEFAULT = 0; 770 771 /** 772 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 773 * items smaller, or at a different position in the list, compared with your app's 774 * {@link #PRIORITY_DEFAULT} items. 775 * 776 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. 777 */ 778 @Deprecated 779 public static final int PRIORITY_LOW = -1; 780 781 /** 782 * Lowest {@link #priority}; these items might not be shown to the user except under special 783 * circumstances, such as detailed notification logs. 784 * 785 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. 786 */ 787 @Deprecated 788 public static final int PRIORITY_MIN = -2; 789 790 /** 791 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 792 * show these items larger, or at a different position in notification lists, compared with 793 * your app's {@link #PRIORITY_DEFAULT} items. 794 * 795 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 796 */ 797 @Deprecated 798 public static final int PRIORITY_HIGH = 1; 799 800 /** 801 * Highest {@link #priority}, for your application's most important items that require the 802 * user's prompt attention or input. 803 * 804 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 805 */ 806 @Deprecated 807 public static final int PRIORITY_MAX = 2; 808 809 /** 810 * Relative priority for this notification. 811 * 812 * Priority is an indication of how much of the user's valuable attention should be consumed by 813 * this notification. Low-priority notifications may be hidden from the user in certain 814 * situations, while the user might be interrupted for a higher-priority notification. The 815 * system will make a determination about how to interpret this priority when presenting 816 * the notification. 817 * 818 * <p> 819 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 820 * as a heads-up notification. 821 * </p> 822 * 823 * @deprecated use {@link NotificationChannel#getImportance()} instead. 824 */ 825 @Priority 826 @Deprecated 827 public int priority; 828 829 /** 830 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 831 * to be applied by the standard Style templates when presenting this notification. 832 * 833 * The current template design constructs a colorful header image by overlaying the 834 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 835 * ignored. 836 */ 837 @ColorInt 838 public int color = COLOR_DEFAULT; 839 840 /** 841 * Special value of {@link #color} telling the system not to decorate this notification with 842 * any special color but instead use default colors when presenting this notification. 843 */ 844 @ColorInt 845 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 846 847 /** 848 * Special value of {@link #color} used as a place holder for an invalid color. 849 * @hide 850 */ 851 @ColorInt 852 public static final int COLOR_INVALID = 1; 853 854 /** 855 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 856 * the notification's presence and contents in untrusted situations (namely, on the secure 857 * lockscreen). 858 * 859 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 860 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 861 * shown in all situations, but the contents are only available if the device is unlocked for 862 * the appropriate user. 863 * 864 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 865 * can be read even in an "insecure" context (that is, above a secure lockscreen). 866 * To modify the public version of this notification—for example, to redact some portions—see 867 * {@link Builder#setPublicVersion(Notification)}. 868 * 869 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 870 * and ticker until the user has bypassed the lockscreen. 871 */ 872 public @Visibility int visibility; 873 874 /** @hide */ 875 @IntDef(prefix = { "VISIBILITY_" }, value = { 876 VISIBILITY_PUBLIC, 877 VISIBILITY_PRIVATE, 878 VISIBILITY_SECRET, 879 }) 880 @Retention(RetentionPolicy.SOURCE) 881 public @interface Visibility {} 882 883 /** 884 * Notification visibility: Show this notification in its entirety on all lockscreens. 885 * 886 * {@see #visibility} 887 */ 888 public static final int VISIBILITY_PUBLIC = 1; 889 890 /** 891 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 892 * private information on secure lockscreens. 893 * 894 * {@see #visibility} 895 */ 896 public static final int VISIBILITY_PRIVATE = 0; 897 898 /** 899 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 900 * 901 * {@see #visibility} 902 */ 903 public static final int VISIBILITY_SECRET = -1; 904 905 /** 906 * @hide 907 */ 908 @IntDef(prefix = "VISIBILITY_", value = { 909 VISIBILITY_PUBLIC, 910 VISIBILITY_PRIVATE, 911 VISIBILITY_SECRET, 912 NotificationManager.VISIBILITY_NO_OVERRIDE 913 }) 914 public @interface NotificationVisibilityOverride{}; 915 916 /** 917 * Notification category: incoming call (voice or video) or similar synchronous communication request. 918 */ 919 public static final String CATEGORY_CALL = "call"; 920 921 /** 922 * Notification category: map turn-by-turn navigation. 923 */ 924 public static final String CATEGORY_NAVIGATION = "navigation"; 925 926 /** 927 * Notification category: incoming direct message (SMS, instant message, etc.). 928 */ 929 public static final String CATEGORY_MESSAGE = "msg"; 930 931 /** 932 * Notification category: asynchronous bulk message (email). 933 */ 934 public static final String CATEGORY_EMAIL = "email"; 935 936 /** 937 * Notification category: calendar event. 938 */ 939 public static final String CATEGORY_EVENT = "event"; 940 941 /** 942 * Notification category: promotion or advertisement. 943 */ 944 public static final String CATEGORY_PROMO = "promo"; 945 946 /** 947 * Notification category: alarm or timer. 948 */ 949 public static final String CATEGORY_ALARM = "alarm"; 950 951 /** 952 * Notification category: progress of a long-running background operation. 953 */ 954 public static final String CATEGORY_PROGRESS = "progress"; 955 956 /** 957 * Notification category: social network or sharing update. 958 */ 959 public static final String CATEGORY_SOCIAL = "social"; 960 961 /** 962 * Notification category: error in background operation or authentication status. 963 */ 964 public static final String CATEGORY_ERROR = "err"; 965 966 /** 967 * Notification category: media transport control for playback. 968 */ 969 public static final String CATEGORY_TRANSPORT = "transport"; 970 971 /** 972 * Notification category: system or device status update. Reserved for system use. 973 */ 974 public static final String CATEGORY_SYSTEM = "sys"; 975 976 /** 977 * Notification category: indication of running background service. 978 */ 979 public static final String CATEGORY_SERVICE = "service"; 980 981 /** 982 * Notification category: a specific, timely recommendation for a single thing. 983 * For example, a news app might want to recommend a news story it believes the user will 984 * want to read next. 985 */ 986 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 987 988 /** 989 * Notification category: ongoing information about device or contextual status. 990 */ 991 public static final String CATEGORY_STATUS = "status"; 992 993 /** 994 * Notification category: user-scheduled reminder. 995 */ 996 public static final String CATEGORY_REMINDER = "reminder"; 997 998 /** 999 * Notification category: extreme car emergencies. 1000 * @hide 1001 */ 1002 @SystemApi 1003 public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; 1004 1005 /** 1006 * Notification category: car warnings. 1007 * @hide 1008 */ 1009 @SystemApi 1010 public static final String CATEGORY_CAR_WARNING = "car_warning"; 1011 1012 /** 1013 * Notification category: general car system information. 1014 * @hide 1015 */ 1016 @SystemApi 1017 public static final String CATEGORY_CAR_INFORMATION = "car_information"; 1018 1019 /** 1020 * Notification category: tracking a user's workout. 1021 */ 1022 public static final String CATEGORY_WORKOUT = "workout"; 1023 1024 /** 1025 * Notification category: temporarily sharing location. 1026 */ 1027 public static final String CATEGORY_LOCATION_SHARING = "location_sharing"; 1028 1029 /** 1030 * Notification category: running stopwatch. 1031 */ 1032 public static final String CATEGORY_STOPWATCH = "stopwatch"; 1033 1034 /** 1035 * Notification category: missed call. 1036 */ 1037 public static final String CATEGORY_MISSED_CALL = "missed_call"; 1038 1039 /** 1040 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 1041 * that best describes this Notification. May be used by the system for ranking and filtering. 1042 */ 1043 public String category; 1044 1045 @UnsupportedAppUsage 1046 private String mGroupKey; 1047 1048 /** 1049 * Get the key used to group this notification into a cluster or stack 1050 * with other notifications on devices which support such rendering. 1051 */ getGroup()1052 public String getGroup() { 1053 return mGroupKey; 1054 } 1055 1056 private String mSortKey; 1057 1058 /** 1059 * Get a sort key that orders this notification among other notifications from the 1060 * same package. This can be useful if an external sort was already applied and an app 1061 * would like to preserve this. Notifications will be sorted lexicographically using this 1062 * value, although providing different priorities in addition to providing sort key may 1063 * cause this value to be ignored. 1064 * 1065 * <p>This sort key can also be used to order members of a notification group. See 1066 * {@link Builder#setGroup}. 1067 * 1068 * @see String#compareTo(String) 1069 */ getSortKey()1070 public String getSortKey() { 1071 return mSortKey; 1072 } 1073 1074 /** 1075 * Additional semantic data to be carried around with this Notification. 1076 * <p> 1077 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 1078 * APIs, and are intended to be used by 1079 * {@link android.service.notification.NotificationListenerService} implementations to extract 1080 * detailed information from notification objects. 1081 */ 1082 public Bundle extras = new Bundle(); 1083 1084 /** 1085 * All pending intents in the notification as the system needs to be able to access them but 1086 * touching the extras bundle in the system process is not safe because the bundle may contain 1087 * custom parcelable objects. 1088 * 1089 * @hide 1090 */ 1091 @UnsupportedAppUsage 1092 public ArraySet<PendingIntent> allPendingIntents; 1093 1094 /** 1095 * Token identifying the notification that is applying doze/bgcheck allowlisting to the 1096 * pending intents inside of it, so only those will get the behavior. 1097 * 1098 * @hide 1099 */ 1100 private IBinder mAllowlistToken; 1101 1102 /** 1103 * Must be set by a process to start associating tokens with Notification objects 1104 * coming in to it. This is set by NotificationManagerService. 1105 * 1106 * @hide 1107 */ 1108 static public IBinder processAllowlistToken; 1109 1110 /** 1111 * {@link #extras} key: this is the title of the notification, 1112 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 1113 */ 1114 public static final String EXTRA_TITLE = "android.title"; 1115 1116 /** 1117 * {@link #extras} key: this is the title of the notification when shown in expanded form, 1118 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 1119 */ 1120 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 1121 1122 /** 1123 * {@link #extras} key: this is the main text payload, as supplied to 1124 * {@link Builder#setContentText(CharSequence)}. 1125 */ 1126 public static final String EXTRA_TEXT = "android.text"; 1127 1128 /** 1129 * {@link #extras} key: this is a third line of text, as supplied to 1130 * {@link Builder#setSubText(CharSequence)}. 1131 */ 1132 public static final String EXTRA_SUB_TEXT = "android.subText"; 1133 1134 /** 1135 * {@link #extras} key: this is the remote input history, as supplied to 1136 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1137 * 1138 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 1139 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 1140 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 1141 * notifications once the other party has responded). 1142 * 1143 * The extra with this key is of type CharSequence[] and contains the most recent entry at 1144 * the 0 index, the second most recent at the 1 index, etc. 1145 * 1146 * @see Builder#setRemoteInputHistory(CharSequence[]) 1147 */ 1148 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 1149 1150 1151 /** 1152 * {@link #extras} key: this is a remote input history which can include media messages 1153 * in addition to text, as supplied to 1154 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or 1155 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1156 * 1157 * SystemUI can populate this through 1158 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs 1159 * that have been sent through a {@link RemoteInput} of this Notification. These items can 1160 * represent either media content (specified by a URI and a MIME type) or a text message 1161 * (described by a CharSequence). 1162 * 1163 * To maintain compatibility, this can also be set by apps with 1164 * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a 1165 * {@link RemoteInputHistoryItem} for each of the provided text-only messages. 1166 * 1167 * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most 1168 * recent entry at the 0 index, the second most recent at the 1 index, etc. 1169 * 1170 * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) 1171 * @hide 1172 */ 1173 public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; 1174 1175 /** 1176 * {@link #extras} key: boolean as supplied to 1177 * {@link Builder#setShowRemoteInputSpinner(boolean)}. 1178 * 1179 * If set to true, then the view displaying the remote input history from 1180 * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner. 1181 * 1182 * @see Builder#setShowRemoteInputSpinner(boolean) 1183 * @hide 1184 */ 1185 public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner"; 1186 1187 /** 1188 * {@link #extras} key: boolean as supplied to 1189 * {@link Builder#setHideSmartReplies(boolean)}. 1190 * 1191 * If set to true, then any smart reply buttons will be hidden. 1192 * 1193 * @see Builder#setHideSmartReplies(boolean) 1194 * @hide 1195 */ 1196 public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies"; 1197 1198 /** 1199 * {@link #extras} key: this is a small piece of additional text as supplied to 1200 * {@link Builder#setContentInfo(CharSequence)}. 1201 */ 1202 public static final String EXTRA_INFO_TEXT = "android.infoText"; 1203 1204 /** 1205 * {@link #extras} key: this is a line of summary information intended to be shown 1206 * alongside expanded notifications, as supplied to (e.g.) 1207 * {@link BigTextStyle#setSummaryText(CharSequence)}. 1208 */ 1209 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 1210 1211 /** 1212 * {@link #extras} key: this is the longer text shown in the big form of a 1213 * {@link BigTextStyle} notification, as supplied to 1214 * {@link BigTextStyle#bigText(CharSequence)}. 1215 */ 1216 public static final String EXTRA_BIG_TEXT = "android.bigText"; 1217 1218 /** 1219 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 1220 * supplied to {@link Builder#setSmallIcon(int)}. 1221 * 1222 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources. 1223 */ 1224 @Deprecated 1225 public static final String EXTRA_SMALL_ICON = "android.icon"; 1226 1227 /** 1228 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 1229 * notification payload, as 1230 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 1231 * 1232 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources. 1233 */ 1234 @Deprecated 1235 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 1236 1237 /** 1238 * {@link #extras} key: this is a bitmap to be used instead of the one from 1239 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 1240 * shown in its expanded form, as supplied to 1241 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 1242 */ 1243 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 1244 1245 /** 1246 * {@link #extras} key: this is the progress value supplied to 1247 * {@link Builder#setProgress(int, int, boolean)}. 1248 */ 1249 public static final String EXTRA_PROGRESS = "android.progress"; 1250 1251 /** 1252 * {@link #extras} key: this is the maximum value supplied to 1253 * {@link Builder#setProgress(int, int, boolean)}. 1254 */ 1255 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 1256 1257 /** 1258 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 1259 * {@link Builder#setProgress(int, int, boolean)}. 1260 */ 1261 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 1262 1263 /** 1264 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 1265 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 1266 * {@link Builder#setUsesChronometer(boolean)}. 1267 */ 1268 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 1269 1270 /** 1271 * {@link #extras} key: whether the chronometer set on the notification should count down 1272 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 1273 * This extra is a boolean. The default is false. 1274 */ 1275 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 1276 1277 /** 1278 * {@link #extras} key: whether {@link #when} should be shown, 1279 * as supplied to {@link Builder#setShowWhen(boolean)}. 1280 */ 1281 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 1282 1283 /** 1284 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 1285 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 1286 */ 1287 public static final String EXTRA_PICTURE = "android.picture"; 1288 1289 /** 1290 * {@link #extras} key: this is an {@link Icon} of an image to be 1291 * shown in {@link BigPictureStyle} expanded notifications, supplied to 1292 * {@link BigPictureStyle#bigPicture(Icon)}. 1293 */ 1294 public static final String EXTRA_PICTURE_ICON = "android.pictureIcon"; 1295 1296 /** 1297 * {@link #extras} key: this is a content description of the big picture supplied from 1298 * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to 1299 * {@link BigPictureStyle#setContentDescription(CharSequence)}. 1300 */ 1301 public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = 1302 "android.pictureContentDescription"; 1303 1304 /** 1305 * {@link #extras} key: this is a boolean to indicate that the 1306 * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state 1307 * of a {@link BigPictureStyle} notification. This will replace a 1308 * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided. 1309 */ 1310 public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = 1311 "android.showBigPictureWhenCollapsed"; 1312 1313 /** 1314 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 1315 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 1316 */ 1317 public static final String EXTRA_TEXT_LINES = "android.textLines"; 1318 1319 /** 1320 * {@link #extras} key: A string representing the name of the specific 1321 * {@link android.app.Notification.Style} used to create this notification. 1322 */ 1323 public static final String EXTRA_TEMPLATE = "android.template"; 1324 1325 /** 1326 * {@link #extras} key: A String array containing the people that this notification relates to, 1327 * each of which was supplied to {@link Builder#addPerson(String)}. 1328 * 1329 * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} 1330 */ 1331 public static final String EXTRA_PEOPLE = "android.people"; 1332 1333 /** 1334 * {@link #extras} key: An arrayList of {@link Person} objects containing the people that 1335 * this notification relates to. 1336 */ 1337 public static final String EXTRA_PEOPLE_LIST = "android.people.list"; 1338 1339 /** 1340 * Allow certain system-generated notifications to appear before the device is provisioned. 1341 * Only available to notifications coming from the android package. 1342 * @hide 1343 */ 1344 @SystemApi 1345 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP) 1346 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 1347 1348 /** 1349 * {@link #extras} key: 1350 * flat {@link String} representation of a {@link android.content.ContentUris content URI} 1351 * pointing to an image that can be displayed in the background when the notification is 1352 * selected. Used on television platforms. The URI must point to an image stream suitable for 1353 * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1354 * BitmapFactory.decodeStream}; all other content types will be ignored. 1355 */ 1356 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 1357 1358 /** 1359 * {@link #extras} key: A 1360 * {@link android.media.session.MediaSession.Token} associated with a 1361 * {@link android.app.Notification.MediaStyle} notification. 1362 */ 1363 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 1364 1365 /** 1366 * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session 1367 * associated with a {@link Notification.MediaStyle} notification. This will show in the media 1368 * controls output switcher instead of the local device name. 1369 * @hide 1370 */ 1371 @TestApi 1372 public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; 1373 1374 /** 1375 * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output 1376 * switcher of the media controls for a {@link Notification.MediaStyle} notification. 1377 * @hide 1378 */ 1379 @TestApi 1380 public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; 1381 1382 /** 1383 * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the 1384 * media controls output switcher chip, associated with a {@link Notification.MediaStyle} 1385 * notification. This should launch an activity. 1386 * @hide 1387 */ 1388 @TestApi 1389 public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; 1390 1391 /** 1392 * {@link #extras} key: the indices of actions to be shown in the compact view, 1393 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 1394 */ 1395 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 1396 1397 /** 1398 * {@link #extras} key: the username to be displayed for all messages sent by the user including 1399 * direct replies 1400 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1401 * {@link CharSequence} 1402 * 1403 * @deprecated use {@link #EXTRA_MESSAGING_PERSON} 1404 */ 1405 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 1406 1407 /** 1408 * {@link #extras} key: the person to be displayed for all messages sent by the user including 1409 * direct replies 1410 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1411 * {@link Person} 1412 */ 1413 public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; 1414 1415 /** 1416 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 1417 * represented by a {@link android.app.Notification.MessagingStyle} 1418 */ 1419 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 1420 1421 /** @hide */ 1422 public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon"; 1423 1424 /** @hide */ 1425 public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT = 1426 "android.conversationUnreadMessageCount"; 1427 1428 /** 1429 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 1430 * bundles provided by a 1431 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1432 * array of bundles. 1433 */ 1434 public static final String EXTRA_MESSAGES = "android.messages"; 1435 1436 /** 1437 * {@link #extras} key: an array of 1438 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic} 1439 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a 1440 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1441 * array of bundles. 1442 */ 1443 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; 1444 1445 /** 1446 * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification 1447 * represents a group conversation. 1448 */ 1449 public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; 1450 1451 /** 1452 * {@link #extras} key: the type of call represented by the 1453 * {@link android.app.Notification.CallStyle} notification. This extra is an int. 1454 */ 1455 public static final String EXTRA_CALL_TYPE = "android.callType"; 1456 1457 /** 1458 * {@link #extras} key: whether the {@link android.app.Notification.CallStyle} notification 1459 * is for a call that will activate video when answered. This extra is a boolean. 1460 */ 1461 public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo"; 1462 1463 /** 1464 * {@link #extras} key: the person to be displayed as calling for the 1465 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}. 1466 */ 1467 public static final String EXTRA_CALL_PERSON = "android.callPerson"; 1468 1469 /** 1470 * {@link #extras} key: the icon to be displayed as a verification status of the caller on a 1471 * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}. 1472 */ 1473 public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon"; 1474 1475 /** 1476 * {@link #extras} key: the text to be displayed as a verification status of the caller on a 1477 * {@link android.app.Notification.CallStyle} notification. This extra is a 1478 * {@link CharSequence}. 1479 */ 1480 public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText"; 1481 1482 /** 1483 * {@link #extras} key: the intent to be sent when the users answers a 1484 * {@link android.app.Notification.CallStyle} notification. This extra is a 1485 * {@link PendingIntent}. 1486 */ 1487 public static final String EXTRA_ANSWER_INTENT = "android.answerIntent"; 1488 1489 /** 1490 * {@link #extras} key: the intent to be sent when the users declines a 1491 * {@link android.app.Notification.CallStyle} notification. This extra is a 1492 * {@link PendingIntent}. 1493 */ 1494 public static final String EXTRA_DECLINE_INTENT = "android.declineIntent"; 1495 1496 /** 1497 * {@link #extras} key: the intent to be sent when the users hangs up a 1498 * {@link android.app.Notification.CallStyle} notification. This extra is a 1499 * {@link PendingIntent}. 1500 */ 1501 public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent"; 1502 1503 /** 1504 * {@link #extras} key: the color used as a hint for the Answer action button of a 1505 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1506 */ 1507 public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; 1508 1509 /** 1510 * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a 1511 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1512 */ 1513 public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; 1514 1515 /** 1516 * {@link #extras} key: whether the notification should be colorized as 1517 * supplied to {@link Builder#setColorized(boolean)}. 1518 */ 1519 public static final String EXTRA_COLORIZED = "android.colorized"; 1520 1521 /** 1522 * @hide 1523 */ 1524 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 1525 1526 /** 1527 * @hide 1528 */ 1529 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 1530 1531 /** 1532 * @hide 1533 */ 1534 public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images"; 1535 1536 /** 1537 * {@link #extras} key: the audio contents of this notification. 1538 * 1539 * This is for use when rendering the notification on an audio-focused interface; 1540 * the audio contents are a complete sound sample that contains the contents/body of the 1541 * notification. This may be used in substitute of a Text-to-Speech reading of the 1542 * notification. For example if the notification represents a voice message this should point 1543 * to the audio of that message. 1544 * 1545 * The data stored under this key should be a String representation of a Uri that contains the 1546 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 1547 * 1548 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 1549 * has a field for holding data URI. That field can be used for audio. 1550 * See {@code Message#setData}. 1551 * 1552 * Example usage: 1553 * <pre> 1554 * {@code 1555 * Notification.Builder myBuilder = (build your Notification as normal); 1556 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 1557 * } 1558 * </pre> 1559 */ 1560 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 1561 1562 /** @hide */ 1563 @SystemApi 1564 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME) 1565 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 1566 1567 /** 1568 * This is set on the notifications shown by system_server about apps running foreground 1569 * services. It indicates that the notification should be shown 1570 * only if any of the given apps do not already have a properly tagged 1571 * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. 1572 * This is a string array of all package names of the apps. 1573 * @hide 1574 */ 1575 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; 1576 1577 @UnsupportedAppUsage 1578 private Icon mSmallIcon; 1579 @UnsupportedAppUsage 1580 private Icon mLargeIcon; 1581 1582 @UnsupportedAppUsage 1583 private String mChannelId; 1584 private long mTimeout; 1585 1586 private String mShortcutId; 1587 private LocusId mLocusId; 1588 private CharSequence mSettingsText; 1589 1590 private BubbleMetadata mBubbleMetadata; 1591 1592 /** @hide */ 1593 @IntDef(prefix = { "GROUP_ALERT_" }, value = { 1594 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY 1595 }) 1596 @Retention(RetentionPolicy.SOURCE) 1597 public @interface GroupAlertBehavior {} 1598 1599 /** 1600 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 1601 * group with sound or vibration ought to make sound or vibrate (respectively), so this 1602 * notification will not be muted when it is in a group. 1603 */ 1604 public static final int GROUP_ALERT_ALL = 0; 1605 1606 /** 1607 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 1608 * notification in a group should be silenced (no sound or vibration) even if they are posted 1609 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to 1610 * mute this notification if this notification is a group child. This must be applied to all 1611 * children notifications you want to mute. 1612 * 1613 * <p> For example, you might want to use this constant if you post a number of children 1614 * notifications at once (say, after a periodic sync), and only need to notify the user 1615 * audibly once. 1616 */ 1617 public static final int GROUP_ALERT_SUMMARY = 1; 1618 1619 /** 1620 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 1621 * notification in a group should be silenced (no sound or vibration) even if they are 1622 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant 1623 * to mute this notification if this notification is a group summary. 1624 * 1625 * <p>For example, you might want to use this constant if only the children notifications 1626 * in your group have content and the summary is only used to visually group notifications 1627 * rather than to alert the user that new information is available. 1628 */ 1629 public static final int GROUP_ALERT_CHILDREN = 2; 1630 1631 private int mGroupAlertBehavior = GROUP_ALERT_ALL; 1632 1633 /** 1634 * If this notification is being shown as a badge, always show as a number. 1635 */ 1636 public static final int BADGE_ICON_NONE = 0; 1637 1638 /** 1639 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to 1640 * represent this notification. 1641 */ 1642 public static final int BADGE_ICON_SMALL = 1; 1643 1644 /** 1645 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to 1646 * represent this notification. 1647 */ 1648 public static final int BADGE_ICON_LARGE = 2; 1649 private int mBadgeIcon = BADGE_ICON_NONE; 1650 1651 /** 1652 * Determines whether the platform can generate contextual actions for a notification. 1653 */ 1654 private boolean mAllowSystemGeneratedContextualActions = true; 1655 1656 /** 1657 * Structure to encapsulate a named action that can be shown as part of this notification. 1658 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1659 * selected by the user. 1660 * <p> 1661 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1662 * or {@link Notification.Builder#addAction(Notification.Action)} 1663 * to attach actions. 1664 * <p> 1665 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link 1666 * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while 1667 * processing broadcast receivers or services in response to notification action clicks. To 1668 * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself. 1669 */ 1670 public static class Action implements Parcelable { 1671 /** 1672 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of 1673 * {@link RemoteInput}s. 1674 * 1675 * This is intended for {@link RemoteInput}s that only accept data, meaning 1676 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 1677 * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not 1678 * empty. These {@link RemoteInput}s will be ignored by devices that do not 1679 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 1680 * 1681 * You can test if a RemoteInput matches these constraints using 1682 * {@link RemoteInput#isDataOnly}. 1683 */ 1684 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; 1685 1686 /** 1687 * {@link }: No semantic action defined. 1688 */ 1689 public static final int SEMANTIC_ACTION_NONE = 0; 1690 1691 /** 1692 * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies 1693 * may be appropriate. 1694 */ 1695 public static final int SEMANTIC_ACTION_REPLY = 1; 1696 1697 /** 1698 * {@code SemanticAction}: Mark content as read. 1699 */ 1700 public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; 1701 1702 /** 1703 * {@code SemanticAction}: Mark content as unread. 1704 */ 1705 public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; 1706 1707 /** 1708 * {@code SemanticAction}: Delete the content associated with the notification. This 1709 * could mean deleting an email, message, etc. 1710 */ 1711 public static final int SEMANTIC_ACTION_DELETE = 4; 1712 1713 /** 1714 * {@code SemanticAction}: Archive the content associated with the notification. This 1715 * could mean archiving an email, message, etc. 1716 */ 1717 public static final int SEMANTIC_ACTION_ARCHIVE = 5; 1718 1719 /** 1720 * {@code SemanticAction}: Mute the content associated with the notification. This could 1721 * mean silencing a conversation or currently playing media. 1722 */ 1723 public static final int SEMANTIC_ACTION_MUTE = 6; 1724 1725 /** 1726 * {@code SemanticAction}: Unmute the content associated with the notification. This could 1727 * mean un-silencing a conversation or currently playing media. 1728 */ 1729 public static final int SEMANTIC_ACTION_UNMUTE = 7; 1730 1731 /** 1732 * {@code SemanticAction}: Mark content with a thumbs up. 1733 */ 1734 public static final int SEMANTIC_ACTION_THUMBS_UP = 8; 1735 1736 /** 1737 * {@code SemanticAction}: Mark content with a thumbs down. 1738 */ 1739 public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; 1740 1741 /** 1742 * {@code SemanticAction}: Call a contact, group, etc. 1743 */ 1744 public static final int SEMANTIC_ACTION_CALL = 10; 1745 1746 /** 1747 * {@code SemanticAction}: Mark the conversation associated with the notification as a 1748 * priority. Note that this is only for use by the notification assistant services. The 1749 * type will be ignored for actions an app adds to its own notifications. 1750 * @hide 1751 */ 1752 @SystemApi 1753 public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; 1754 1755 /** 1756 * {@code SemanticAction}: Mark content as a potential phishing attempt. 1757 * Note that this is only for use by the notification assistant services. The type will 1758 * be ignored for actions an app adds to its own notifications. 1759 * @hide 1760 */ 1761 @SystemApi 1762 public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; 1763 1764 private final Bundle mExtras; 1765 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1766 private Icon mIcon; 1767 private final RemoteInput[] mRemoteInputs; 1768 private boolean mAllowGeneratedReplies = true; 1769 private final @SemanticAction int mSemanticAction; 1770 private final boolean mIsContextual; 1771 private boolean mAuthenticationRequired; 1772 1773 /** 1774 * Small icon representing the action. 1775 * 1776 * @deprecated Use {@link Action#getIcon()} instead. 1777 */ 1778 @Deprecated 1779 public int icon; 1780 1781 /** 1782 * Title of the action. 1783 */ 1784 public CharSequence title; 1785 1786 /** 1787 * Intent to send when the user invokes this action. May be null, in which case the action 1788 * may be rendered in a disabled presentation by the system UI. 1789 */ 1790 public PendingIntent actionIntent; 1791 Action(Parcel in)1792 private Action(Parcel in) { 1793 if (in.readInt() != 0) { 1794 mIcon = Icon.CREATOR.createFromParcel(in); 1795 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1796 icon = mIcon.getResId(); 1797 } 1798 } 1799 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1800 if (in.readInt() == 1) { 1801 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1802 } 1803 mExtras = Bundle.setDefusable(in.readBundle(), true); 1804 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1805 mAllowGeneratedReplies = in.readInt() == 1; 1806 mSemanticAction = in.readInt(); 1807 mIsContextual = in.readInt() == 1; 1808 mAuthenticationRequired = in.readInt() == 1; 1809 } 1810 1811 /** 1812 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1813 */ 1814 @Deprecated Action(int icon, CharSequence title, @Nullable PendingIntent intent)1815 public Action(int icon, CharSequence title, @Nullable PendingIntent intent) { 1816 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, 1817 SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */); 1818 } 1819 1820 /** 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)1821 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1822 RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1823 @SemanticAction int semanticAction, boolean isContextual, 1824 boolean requireAuth) { 1825 this.mIcon = icon; 1826 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1827 this.icon = icon.getResId(); 1828 } 1829 this.title = title; 1830 this.actionIntent = intent; 1831 this.mExtras = extras != null ? extras : new Bundle(); 1832 this.mRemoteInputs = remoteInputs; 1833 this.mAllowGeneratedReplies = allowGeneratedReplies; 1834 this.mSemanticAction = semanticAction; 1835 this.mIsContextual = isContextual; 1836 this.mAuthenticationRequired = requireAuth; 1837 } 1838 1839 /** 1840 * Return an icon representing the action. 1841 */ getIcon()1842 public Icon getIcon() { 1843 if (mIcon == null && icon != 0) { 1844 // you snuck an icon in here without using the builder; let's try to keep it 1845 mIcon = Icon.createWithResource("", icon); 1846 } 1847 return mIcon; 1848 } 1849 1850 /** 1851 * Get additional metadata carried around with this Action. 1852 */ getExtras()1853 public Bundle getExtras() { 1854 return mExtras; 1855 } 1856 1857 /** 1858 * Return whether the platform should automatically generate possible replies for this 1859 * {@link Action} 1860 */ getAllowGeneratedReplies()1861 public boolean getAllowGeneratedReplies() { 1862 return mAllowGeneratedReplies; 1863 } 1864 1865 /** 1866 * Get the list of inputs to be collected from the user when this action is sent. 1867 * May return null if no remote inputs were added. Only returns inputs which accept 1868 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 1869 */ getRemoteInputs()1870 public RemoteInput[] getRemoteInputs() { 1871 return mRemoteInputs; 1872 } 1873 1874 /** 1875 * Returns the {@code SemanticAction} associated with this {@link Action}. A 1876 * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do 1877 * (eg. reply, mark as read, delete, etc). 1878 */ getSemanticAction()1879 public @SemanticAction int getSemanticAction() { 1880 return mSemanticAction; 1881 } 1882 1883 /** 1884 * Returns whether this is a contextual Action, i.e. whether the action is dependent on the 1885 * notification message body. An example of a contextual action could be an action opening a 1886 * map application with an address shown in the notification. 1887 */ isContextual()1888 public boolean isContextual() { 1889 return mIsContextual; 1890 } 1891 1892 /** 1893 * Get the list of inputs to be collected from the user that ONLY accept data when this 1894 * action is sent. These remote inputs are guaranteed to return true on a call to 1895 * {@link RemoteInput#isDataOnly}. 1896 * 1897 * Returns null if there are no data-only remote inputs. 1898 * 1899 * This method exists so that legacy RemoteInput collectors that pre-date the addition 1900 * of non-textual RemoteInputs do not access these remote inputs. 1901 */ getDataOnlyRemoteInputs()1902 public RemoteInput[] getDataOnlyRemoteInputs() { 1903 return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 1904 } 1905 1906 /** 1907 * Returns whether the OS should only send this action's {@link PendingIntent} on an 1908 * unlocked device. 1909 * 1910 * If the device is locked when the action is invoked, the OS should show the keyguard and 1911 * require successful authentication before invoking the intent. 1912 */ isAuthenticationRequired()1913 public boolean isAuthenticationRequired() { 1914 return mAuthenticationRequired; 1915 } 1916 1917 /** 1918 * Builder class for {@link Action} objects. 1919 */ 1920 public static final class Builder { 1921 @Nullable private final Icon mIcon; 1922 @Nullable private final CharSequence mTitle; 1923 @Nullable private final PendingIntent mIntent; 1924 private boolean mAllowGeneratedReplies = true; 1925 @NonNull private final Bundle mExtras; 1926 @Nullable private ArrayList<RemoteInput> mRemoteInputs; 1927 private @SemanticAction int mSemanticAction; 1928 private boolean mIsContextual; 1929 private boolean mAuthenticationRequired; 1930 1931 /** 1932 * Construct a new builder for {@link Action} object. 1933 * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, 1934 * action button icons will not be displayed on action buttons, but are still required 1935 * and are available to 1936 * {@link android.service.notification.NotificationListenerService notification listeners}, 1937 * which may display them in other contexts, for example on a wearable device. 1938 * @param icon icon to show for this action 1939 * @param title the title of the action 1940 * @param intent the {@link PendingIntent} to fire when users trigger this action. May 1941 * be null, in which case the action may be rendered in a disabled presentation by the 1942 * system UI. 1943 */ 1944 @Deprecated Builder(int icon, CharSequence title, @Nullable PendingIntent intent)1945 public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) { 1946 this(Icon.createWithResource("", icon), title, intent); 1947 } 1948 1949 /** 1950 * Construct a new builder for {@link Action} object. 1951 * 1952 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 1953 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 1954 * while processing broadcast receivers or services in response to notification action 1955 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the 1956 * activity itself. 1957 * 1958 * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or 1959 * both are displayed or required, depends on where and how the action is used, and the 1960 * {@link Style} applied to the Notification. 1961 * 1962 * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons 1963 * will not be displayed on action buttons, but are still required and are available 1964 * to {@link android.service.notification.NotificationListenerService notification 1965 * listeners}, which may display them in other contexts, for example on a wearable 1966 * device. 1967 * 1968 * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a 1969 * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed 1970 * with an altered in luminance to ensure proper contrast within the Notification. 1971 * 1972 * @param icon icon to show for this action 1973 * @param title the title of the action 1974 * @param intent the {@link PendingIntent} to fire when users trigger this action. May 1975 * be null, in which case the action may be rendered in a disabled presentation by the 1976 * system UI. 1977 */ Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent)1978 public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) { 1979 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false); 1980 } 1981 1982 /** 1983 * Construct a new builder for {@link Action} object using the fields from an 1984 * {@link Action}. 1985 * @param action the action to read fields from. 1986 */ Builder(Action action)1987 public Builder(Action action) { 1988 this(action.getIcon(), action.title, action.actionIntent, 1989 new Bundle(action.mExtras), action.getRemoteInputs(), 1990 action.getAllowGeneratedReplies(), action.getSemanticAction(), 1991 action.isAuthenticationRequired()); 1992 } 1993 Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1994 private Builder(@Nullable Icon icon, @Nullable CharSequence title, 1995 @Nullable PendingIntent intent, @NonNull Bundle extras, 1996 @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1997 @SemanticAction int semanticAction, boolean authRequired) { 1998 mIcon = icon; 1999 mTitle = title; 2000 mIntent = intent; 2001 mExtras = extras; 2002 if (remoteInputs != null) { 2003 mRemoteInputs = new ArrayList<>(remoteInputs.length); 2004 Collections.addAll(mRemoteInputs, remoteInputs); 2005 } 2006 mAllowGeneratedReplies = allowGeneratedReplies; 2007 mSemanticAction = semanticAction; 2008 mAuthenticationRequired = authRequired; 2009 } 2010 2011 /** 2012 * Merge additional metadata into this builder. 2013 * 2014 * <p>Values within the Bundle will replace existing extras values in this Builder. 2015 * 2016 * @see Notification.Action#extras 2017 */ 2018 @NonNull addExtras(Bundle extras)2019 public Builder addExtras(Bundle extras) { 2020 if (extras != null) { 2021 mExtras.putAll(extras); 2022 } 2023 return this; 2024 } 2025 2026 /** 2027 * Get the metadata Bundle used by this Builder. 2028 * 2029 * <p>The returned Bundle is shared with this Builder. 2030 */ 2031 @NonNull getExtras()2032 public Bundle getExtras() { 2033 return mExtras; 2034 } 2035 2036 /** 2037 * Add an input to be collected from the user when this action is sent. 2038 * Response values can be retrieved from the fired intent by using the 2039 * {@link RemoteInput#getResultsFromIntent} function. 2040 * @param remoteInput a {@link RemoteInput} to add to the action 2041 * @return this object for method chaining 2042 */ 2043 @NonNull addRemoteInput(RemoteInput remoteInput)2044 public Builder addRemoteInput(RemoteInput remoteInput) { 2045 if (mRemoteInputs == null) { 2046 mRemoteInputs = new ArrayList<RemoteInput>(); 2047 } 2048 mRemoteInputs.add(remoteInput); 2049 return this; 2050 } 2051 2052 /** 2053 * Set whether the platform should automatically generate possible replies to add to 2054 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 2055 * {@link RemoteInput}, this has no effect. 2056 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 2057 * otherwise 2058 * @return this object for method chaining 2059 * The default value is {@code true} 2060 */ 2061 @NonNull setAllowGeneratedReplies(boolean allowGeneratedReplies)2062 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 2063 mAllowGeneratedReplies = allowGeneratedReplies; 2064 return this; 2065 } 2066 2067 /** 2068 * Sets the {@code SemanticAction} for this {@link Action}. A 2069 * {@code SemanticAction} denotes what an {@link Action}'s 2070 * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). 2071 * @param semanticAction a SemanticAction defined within {@link Action} with 2072 * {@code SEMANTIC_ACTION_} prefixes 2073 * @return this object for method chaining 2074 */ 2075 @NonNull setSemanticAction(@emanticAction int semanticAction)2076 public Builder setSemanticAction(@SemanticAction int semanticAction) { 2077 mSemanticAction = semanticAction; 2078 return this; 2079 } 2080 2081 /** 2082 * Sets whether this {@link Action} is a contextual action, i.e. whether the action is 2083 * dependent on the notification message body. An example of a contextual action could 2084 * be an action opening a map application with an address shown in the notification. 2085 */ 2086 @NonNull setContextual(boolean isContextual)2087 public Builder setContextual(boolean isContextual) { 2088 mIsContextual = isContextual; 2089 return this; 2090 } 2091 2092 /** 2093 * Apply an extender to this action builder. Extenders may be used to add 2094 * metadata or change options on this builder. 2095 */ 2096 @NonNull extend(Extender extender)2097 public Builder extend(Extender extender) { 2098 extender.extend(this); 2099 return this; 2100 } 2101 2102 /** 2103 * Sets whether the OS should only send this action's {@link PendingIntent} on an 2104 * unlocked device. 2105 * 2106 * If this is true and the device is locked when the action is invoked, the OS will 2107 * show the keyguard and require successful authentication before invoking the intent. 2108 * If this is false and the device is locked, the OS will decide whether authentication 2109 * should be required. 2110 */ 2111 @NonNull setAuthenticationRequired(boolean authenticationRequired)2112 public Builder setAuthenticationRequired(boolean authenticationRequired) { 2113 mAuthenticationRequired = authenticationRequired; 2114 return this; 2115 } 2116 2117 /** 2118 * Throws an NPE if we are building a contextual action missing one of the fields 2119 * necessary to display the action. 2120 */ checkContextualActionNullFields()2121 private void checkContextualActionNullFields() { 2122 if (!mIsContextual) return; 2123 2124 if (mIcon == null) { 2125 throw new NullPointerException("Contextual Actions must contain a valid icon"); 2126 } 2127 2128 if (mIntent == null) { 2129 throw new NullPointerException( 2130 "Contextual Actions must contain a valid PendingIntent"); 2131 } 2132 } 2133 2134 /** 2135 * Combine all of the options that have been set and return a new {@link Action} 2136 * object. 2137 * @return the built action 2138 */ 2139 @NonNull build()2140 public Action build() { 2141 checkContextualActionNullFields(); 2142 2143 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>(); 2144 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle( 2145 mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 2146 if (previousDataInputs != null) { 2147 for (RemoteInput input : previousDataInputs) { 2148 dataOnlyInputs.add(input); 2149 } 2150 } 2151 List<RemoteInput> textInputs = new ArrayList<>(); 2152 if (mRemoteInputs != null) { 2153 for (RemoteInput input : mRemoteInputs) { 2154 if (input.isDataOnly()) { 2155 dataOnlyInputs.add(input); 2156 } else { 2157 textInputs.add(input); 2158 } 2159 } 2160 } 2161 if (!dataOnlyInputs.isEmpty()) { 2162 RemoteInput[] dataInputsArr = 2163 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 2164 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); 2165 } 2166 RemoteInput[] textInputsArr = textInputs.isEmpty() 2167 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 2168 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 2169 mAllowGeneratedReplies, mSemanticAction, mIsContextual, 2170 mAuthenticationRequired); 2171 } 2172 } 2173 visitUris(@onNull Consumer<Uri> visitor)2174 private void visitUris(@NonNull Consumer<Uri> visitor) { 2175 visitIconUri(visitor, getIcon()); 2176 } 2177 2178 @Override clone()2179 public Action clone() { 2180 return new Action( 2181 getIcon(), 2182 title, 2183 actionIntent, // safe to alias 2184 mExtras == null ? new Bundle() : new Bundle(mExtras), 2185 getRemoteInputs(), 2186 getAllowGeneratedReplies(), 2187 getSemanticAction(), 2188 isContextual(), 2189 isAuthenticationRequired()); 2190 } 2191 2192 @Override describeContents()2193 public int describeContents() { 2194 return 0; 2195 } 2196 2197 @Override writeToParcel(Parcel out, int flags)2198 public void writeToParcel(Parcel out, int flags) { 2199 final Icon ic = getIcon(); 2200 if (ic != null) { 2201 out.writeInt(1); 2202 ic.writeToParcel(out, 0); 2203 } else { 2204 out.writeInt(0); 2205 } 2206 TextUtils.writeToParcel(title, out, flags); 2207 if (actionIntent != null) { 2208 out.writeInt(1); 2209 actionIntent.writeToParcel(out, flags); 2210 } else { 2211 out.writeInt(0); 2212 } 2213 out.writeBundle(mExtras); 2214 out.writeTypedArray(mRemoteInputs, flags); 2215 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 2216 out.writeInt(mSemanticAction); 2217 out.writeInt(mIsContextual ? 1 : 0); 2218 out.writeInt(mAuthenticationRequired ? 1 : 0); 2219 } 2220 2221 public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR = 2222 new Parcelable.Creator<Action>() { 2223 public Action createFromParcel(Parcel in) { 2224 return new Action(in); 2225 } 2226 public Action[] newArray(int size) { 2227 return new Action[size]; 2228 } 2229 }; 2230 2231 /** 2232 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 2233 * metadata or change options on an action builder. 2234 */ 2235 public interface Extender { 2236 /** 2237 * Apply this extender to a notification action builder. 2238 * @param builder the builder to be modified. 2239 * @return the build object for chaining. 2240 */ extend(Builder builder)2241 public Builder extend(Builder builder); 2242 } 2243 2244 /** 2245 * Wearable extender for notification actions. To add extensions to an action, 2246 * create a new {@link android.app.Notification.Action.WearableExtender} object using 2247 * the {@code WearableExtender()} constructor and apply it to a 2248 * {@link android.app.Notification.Action.Builder} using 2249 * {@link android.app.Notification.Action.Builder#extend}. 2250 * 2251 * <pre class="prettyprint"> 2252 * Notification.Action action = new Notification.Action.Builder( 2253 * R.drawable.archive_all, "Archive all", actionIntent) 2254 * .extend(new Notification.Action.WearableExtender() 2255 * .setAvailableOffline(false)) 2256 * .build();</pre> 2257 */ 2258 public static final class WearableExtender implements Extender { 2259 /** Notification action extra which contains wearable extensions */ 2260 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 2261 2262 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 2263 private static final String KEY_FLAGS = "flags"; 2264 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 2265 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 2266 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 2267 2268 // Flags bitwise-ored to mFlags 2269 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 2270 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 2271 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 2272 2273 // Default value for flags integer 2274 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 2275 2276 private int mFlags = DEFAULT_FLAGS; 2277 2278 private CharSequence mInProgressLabel; 2279 private CharSequence mConfirmLabel; 2280 private CharSequence mCancelLabel; 2281 2282 /** 2283 * Create a {@link android.app.Notification.Action.WearableExtender} with default 2284 * options. 2285 */ WearableExtender()2286 public WearableExtender() { 2287 } 2288 2289 /** 2290 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 2291 * wearable options present in an existing notification action. 2292 * @param action the notification action to inspect. 2293 */ WearableExtender(Action action)2294 public WearableExtender(Action action) { 2295 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 2296 if (wearableBundle != null) { 2297 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 2298 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 2299 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 2300 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 2301 } 2302 } 2303 2304 /** 2305 * Apply wearable extensions to a notification action that is being built. This is 2306 * typically called by the {@link android.app.Notification.Action.Builder#extend} 2307 * method of {@link android.app.Notification.Action.Builder}. 2308 */ 2309 @Override extend(Action.Builder builder)2310 public Action.Builder extend(Action.Builder builder) { 2311 Bundle wearableBundle = new Bundle(); 2312 2313 if (mFlags != DEFAULT_FLAGS) { 2314 wearableBundle.putInt(KEY_FLAGS, mFlags); 2315 } 2316 if (mInProgressLabel != null) { 2317 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 2318 } 2319 if (mConfirmLabel != null) { 2320 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 2321 } 2322 if (mCancelLabel != null) { 2323 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 2324 } 2325 2326 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 2327 return builder; 2328 } 2329 2330 @Override clone()2331 public WearableExtender clone() { 2332 WearableExtender that = new WearableExtender(); 2333 that.mFlags = this.mFlags; 2334 that.mInProgressLabel = this.mInProgressLabel; 2335 that.mConfirmLabel = this.mConfirmLabel; 2336 that.mCancelLabel = this.mCancelLabel; 2337 return that; 2338 } 2339 2340 /** 2341 * Set whether this action is available when the wearable device is not connected to 2342 * a companion device. The user can still trigger this action when the wearable device is 2343 * offline, but a visual hint will indicate that the action may not be available. 2344 * Defaults to true. 2345 */ setAvailableOffline(boolean availableOffline)2346 public WearableExtender setAvailableOffline(boolean availableOffline) { 2347 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 2348 return this; 2349 } 2350 2351 /** 2352 * Get whether this action is available when the wearable device is not connected to 2353 * a companion device. The user can still trigger this action when the wearable device is 2354 * offline, but a visual hint will indicate that the action may not be available. 2355 * Defaults to true. 2356 */ isAvailableOffline()2357 public boolean isAvailableOffline() { 2358 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 2359 } 2360 setFlag(int mask, boolean value)2361 private void setFlag(int mask, boolean value) { 2362 if (value) { 2363 mFlags |= mask; 2364 } else { 2365 mFlags &= ~mask; 2366 } 2367 } 2368 2369 /** 2370 * Set a label to display while the wearable is preparing to automatically execute the 2371 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2372 * 2373 * @param label the label to display while the action is being prepared to execute 2374 * @return this object for method chaining 2375 */ 2376 @Deprecated setInProgressLabel(CharSequence label)2377 public WearableExtender setInProgressLabel(CharSequence label) { 2378 mInProgressLabel = label; 2379 return this; 2380 } 2381 2382 /** 2383 * Get the label to display while the wearable is preparing to automatically execute 2384 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2385 * 2386 * @return the label to display while the action is being prepared to execute 2387 */ 2388 @Deprecated getInProgressLabel()2389 public CharSequence getInProgressLabel() { 2390 return mInProgressLabel; 2391 } 2392 2393 /** 2394 * Set a label to display to confirm that the action should be executed. 2395 * This is usually an imperative verb like "Send". 2396 * 2397 * @param label the label to confirm the action should be executed 2398 * @return this object for method chaining 2399 */ 2400 @Deprecated setConfirmLabel(CharSequence label)2401 public WearableExtender setConfirmLabel(CharSequence label) { 2402 mConfirmLabel = label; 2403 return this; 2404 } 2405 2406 /** 2407 * Get the label to display to confirm that the action should be executed. 2408 * This is usually an imperative verb like "Send". 2409 * 2410 * @return the label to confirm the action should be executed 2411 */ 2412 @Deprecated getConfirmLabel()2413 public CharSequence getConfirmLabel() { 2414 return mConfirmLabel; 2415 } 2416 2417 /** 2418 * Set a label to display to cancel the action. 2419 * This is usually an imperative verb, like "Cancel". 2420 * 2421 * @param label the label to display to cancel the action 2422 * @return this object for method chaining 2423 */ 2424 @Deprecated setCancelLabel(CharSequence label)2425 public WearableExtender setCancelLabel(CharSequence label) { 2426 mCancelLabel = label; 2427 return this; 2428 } 2429 2430 /** 2431 * Get the label to display to cancel the action. 2432 * This is usually an imperative verb like "Cancel". 2433 * 2434 * @return the label to display to cancel the action 2435 */ 2436 @Deprecated getCancelLabel()2437 public CharSequence getCancelLabel() { 2438 return mCancelLabel; 2439 } 2440 2441 /** 2442 * Set a hint that this Action will launch an {@link Activity} directly, telling the 2443 * platform that it can generate the appropriate transitions. 2444 * @param hintLaunchesActivity {@code true} if the content intent will launch 2445 * an activity and transitions should be generated, false otherwise. 2446 * @return this object for method chaining 2447 */ setHintLaunchesActivity( boolean hintLaunchesActivity)2448 public WearableExtender setHintLaunchesActivity( 2449 boolean hintLaunchesActivity) { 2450 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 2451 return this; 2452 } 2453 2454 /** 2455 * Get a hint that this Action will launch an {@link Activity} directly, telling the 2456 * platform that it can generate the appropriate transitions 2457 * @return {@code true} if the content intent will launch an activity and transitions 2458 * should be generated, false otherwise. The default value is {@code false} if this was 2459 * never set. 2460 */ getHintLaunchesActivity()2461 public boolean getHintLaunchesActivity() { 2462 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 2463 } 2464 2465 /** 2466 * Set a hint that this Action should be displayed inline. 2467 * 2468 * @param hintDisplayInline {@code true} if action should be displayed inline, false 2469 * otherwise 2470 * @return this object for method chaining 2471 */ setHintDisplayActionInline( boolean hintDisplayInline)2472 public WearableExtender setHintDisplayActionInline( 2473 boolean hintDisplayInline) { 2474 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 2475 return this; 2476 } 2477 2478 /** 2479 * Get a hint that this Action should be displayed inline. 2480 * 2481 * @return {@code true} if the Action should be displayed inline, {@code false} 2482 * otherwise. The default value is {@code false} if this was never set. 2483 */ getHintDisplayActionInline()2484 public boolean getHintDisplayActionInline() { 2485 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 2486 } 2487 } 2488 2489 /** 2490 * Provides meaning to an {@link Action} that hints at what the associated 2491 * {@link PendingIntent} will do. For example, an {@link Action} with a 2492 * {@link PendingIntent} that replies to a text message notification may have the 2493 * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. 2494 * 2495 * @hide 2496 */ 2497 @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { 2498 SEMANTIC_ACTION_NONE, 2499 SEMANTIC_ACTION_REPLY, 2500 SEMANTIC_ACTION_MARK_AS_READ, 2501 SEMANTIC_ACTION_MARK_AS_UNREAD, 2502 SEMANTIC_ACTION_DELETE, 2503 SEMANTIC_ACTION_ARCHIVE, 2504 SEMANTIC_ACTION_MUTE, 2505 SEMANTIC_ACTION_UNMUTE, 2506 SEMANTIC_ACTION_THUMBS_UP, 2507 SEMANTIC_ACTION_THUMBS_DOWN, 2508 SEMANTIC_ACTION_CALL, 2509 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY, 2510 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING 2511 }) 2512 @Retention(RetentionPolicy.SOURCE) 2513 public @interface SemanticAction {} 2514 } 2515 2516 /** 2517 * Array of all {@link Action} structures attached to this notification by 2518 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 2519 * {@link android.service.notification.NotificationListenerService} that provide an alternative 2520 * interface for invoking actions. 2521 */ 2522 public Action[] actions; 2523 2524 /** 2525 * Replacement version of this notification whose content will be shown 2526 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 2527 * and {@link #VISIBILITY_PUBLIC}. 2528 */ 2529 public Notification publicVersion; 2530 2531 /** 2532 * Constructs a Notification object with default values. 2533 * You might want to consider using {@link Builder} instead. 2534 */ Notification()2535 public Notification() 2536 { 2537 this.when = System.currentTimeMillis(); 2538 this.creationTime = System.currentTimeMillis(); 2539 this.priority = PRIORITY_DEFAULT; 2540 } 2541 2542 /** 2543 * @hide 2544 */ 2545 @UnsupportedAppUsage Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2546 public Notification(Context context, int icon, CharSequence tickerText, long when, 2547 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 2548 { 2549 new Builder(context) 2550 .setWhen(when) 2551 .setSmallIcon(icon) 2552 .setTicker(tickerText) 2553 .setContentTitle(contentTitle) 2554 .setContentText(contentText) 2555 .setContentIntent(PendingIntent.getActivity( 2556 context, 0, contentIntent, PendingIntent.FLAG_MUTABLE)) 2557 .buildInto(this); 2558 } 2559 2560 /** 2561 * Constructs a Notification object with the information needed to 2562 * have a status bar icon without the standard expanded view. 2563 * 2564 * @param icon The resource id of the icon to put in the status bar. 2565 * @param tickerText The text that flows by in the status bar when the notification first 2566 * activates. 2567 * @param when The time to show in the time field. In the System.currentTimeMillis 2568 * timebase. 2569 * 2570 * @deprecated Use {@link Builder} instead. 2571 */ 2572 @Deprecated Notification(int icon, CharSequence tickerText, long when)2573 public Notification(int icon, CharSequence tickerText, long when) 2574 { 2575 this.icon = icon; 2576 this.tickerText = tickerText; 2577 this.when = when; 2578 this.creationTime = System.currentTimeMillis(); 2579 } 2580 2581 /** 2582 * Unflatten the notification from a parcel. 2583 */ 2584 @SuppressWarnings("unchecked") Notification(Parcel parcel)2585 public Notification(Parcel parcel) { 2586 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 2587 // intents in extras are always written as the last entry. 2588 readFromParcelImpl(parcel); 2589 // Must be read last! 2590 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 2591 } 2592 readFromParcelImpl(Parcel parcel)2593 private void readFromParcelImpl(Parcel parcel) 2594 { 2595 int version = parcel.readInt(); 2596 2597 mAllowlistToken = parcel.readStrongBinder(); 2598 if (mAllowlistToken == null) { 2599 mAllowlistToken = processAllowlistToken; 2600 } 2601 // Propagate this token to all pending intents that are unmarshalled from the parcel. 2602 parcel.setClassCookie(PendingIntent.class, mAllowlistToken); 2603 2604 when = parcel.readLong(); 2605 creationTime = parcel.readLong(); 2606 if (parcel.readInt() != 0) { 2607 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 2608 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 2609 icon = mSmallIcon.getResId(); 2610 } 2611 } 2612 number = parcel.readInt(); 2613 if (parcel.readInt() != 0) { 2614 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2615 } 2616 if (parcel.readInt() != 0) { 2617 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2618 } 2619 if (parcel.readInt() != 0) { 2620 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2621 } 2622 if (parcel.readInt() != 0) { 2623 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 2624 } 2625 if (parcel.readInt() != 0) { 2626 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 2627 } 2628 if (parcel.readInt() != 0) { 2629 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 2630 } 2631 defaults = parcel.readInt(); 2632 flags = parcel.readInt(); 2633 if (parcel.readInt() != 0) { 2634 sound = Uri.CREATOR.createFromParcel(parcel); 2635 } 2636 2637 audioStreamType = parcel.readInt(); 2638 if (parcel.readInt() != 0) { 2639 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 2640 } 2641 vibrate = parcel.createLongArray(); 2642 ledARGB = parcel.readInt(); 2643 ledOnMS = parcel.readInt(); 2644 ledOffMS = parcel.readInt(); 2645 iconLevel = parcel.readInt(); 2646 2647 if (parcel.readInt() != 0) { 2648 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2649 } 2650 2651 priority = parcel.readInt(); 2652 2653 category = parcel.readString8(); 2654 2655 mGroupKey = parcel.readString8(); 2656 2657 mSortKey = parcel.readString8(); 2658 2659 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 2660 fixDuplicateExtras(); 2661 2662 actions = parcel.createTypedArray(Action.CREATOR); // may be null 2663 2664 if (parcel.readInt() != 0) { 2665 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2666 } 2667 2668 if (parcel.readInt() != 0) { 2669 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2670 } 2671 2672 visibility = parcel.readInt(); 2673 2674 if (parcel.readInt() != 0) { 2675 publicVersion = Notification.CREATOR.createFromParcel(parcel); 2676 } 2677 2678 color = parcel.readInt(); 2679 2680 if (parcel.readInt() != 0) { 2681 mChannelId = parcel.readString8(); 2682 } 2683 mTimeout = parcel.readLong(); 2684 2685 if (parcel.readInt() != 0) { 2686 mShortcutId = parcel.readString8(); 2687 } 2688 2689 if (parcel.readInt() != 0) { 2690 mLocusId = LocusId.CREATOR.createFromParcel(parcel); 2691 } 2692 2693 mBadgeIcon = parcel.readInt(); 2694 2695 if (parcel.readInt() != 0) { 2696 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2697 } 2698 2699 mGroupAlertBehavior = parcel.readInt(); 2700 if (parcel.readInt() != 0) { 2701 mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel); 2702 } 2703 2704 mAllowSystemGeneratedContextualActions = parcel.readBoolean(); 2705 2706 mFgsDeferBehavior = parcel.readInt(); 2707 } 2708 2709 @Override clone()2710 public Notification clone() { 2711 Notification that = new Notification(); 2712 cloneInto(that, true); 2713 return that; 2714 } 2715 2716 /** 2717 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 2718 * of this into that. 2719 * @hide 2720 */ cloneInto(Notification that, boolean heavy)2721 public void cloneInto(Notification that, boolean heavy) { 2722 that.mAllowlistToken = this.mAllowlistToken; 2723 that.when = this.when; 2724 that.creationTime = this.creationTime; 2725 that.mSmallIcon = this.mSmallIcon; 2726 that.number = this.number; 2727 2728 // PendingIntents are global, so there's no reason (or way) to clone them. 2729 that.contentIntent = this.contentIntent; 2730 that.deleteIntent = this.deleteIntent; 2731 that.fullScreenIntent = this.fullScreenIntent; 2732 2733 if (this.tickerText != null) { 2734 that.tickerText = this.tickerText.toString(); 2735 } 2736 if (heavy && this.tickerView != null) { 2737 that.tickerView = this.tickerView.clone(); 2738 } 2739 if (heavy && this.contentView != null) { 2740 that.contentView = this.contentView.clone(); 2741 } 2742 if (heavy && this.mLargeIcon != null) { 2743 that.mLargeIcon = this.mLargeIcon; 2744 } 2745 that.iconLevel = this.iconLevel; 2746 that.sound = this.sound; // android.net.Uri is immutable 2747 that.audioStreamType = this.audioStreamType; 2748 if (this.audioAttributes != null) { 2749 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 2750 } 2751 2752 final long[] vibrate = this.vibrate; 2753 if (vibrate != null) { 2754 final int N = vibrate.length; 2755 final long[] vib = that.vibrate = new long[N]; 2756 System.arraycopy(vibrate, 0, vib, 0, N); 2757 } 2758 2759 that.ledARGB = this.ledARGB; 2760 that.ledOnMS = this.ledOnMS; 2761 that.ledOffMS = this.ledOffMS; 2762 that.defaults = this.defaults; 2763 2764 that.flags = this.flags; 2765 2766 that.priority = this.priority; 2767 2768 that.category = this.category; 2769 2770 that.mGroupKey = this.mGroupKey; 2771 2772 that.mSortKey = this.mSortKey; 2773 2774 if (this.extras != null) { 2775 try { 2776 that.extras = new Bundle(this.extras); 2777 // will unparcel 2778 that.extras.size(); 2779 } catch (BadParcelableException e) { 2780 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 2781 that.extras = null; 2782 } 2783 } 2784 2785 if (!ArrayUtils.isEmpty(allPendingIntents)) { 2786 that.allPendingIntents = new ArraySet<>(allPendingIntents); 2787 } 2788 2789 if (this.actions != null) { 2790 that.actions = new Action[this.actions.length]; 2791 for(int i=0; i<this.actions.length; i++) { 2792 if ( this.actions[i] != null) { 2793 that.actions[i] = this.actions[i].clone(); 2794 } 2795 } 2796 } 2797 2798 if (heavy && this.bigContentView != null) { 2799 that.bigContentView = this.bigContentView.clone(); 2800 } 2801 2802 if (heavy && this.headsUpContentView != null) { 2803 that.headsUpContentView = this.headsUpContentView.clone(); 2804 } 2805 2806 that.visibility = this.visibility; 2807 2808 if (this.publicVersion != null) { 2809 that.publicVersion = new Notification(); 2810 this.publicVersion.cloneInto(that.publicVersion, heavy); 2811 } 2812 2813 that.color = this.color; 2814 2815 that.mChannelId = this.mChannelId; 2816 that.mTimeout = this.mTimeout; 2817 that.mShortcutId = this.mShortcutId; 2818 that.mLocusId = this.mLocusId; 2819 that.mBadgeIcon = this.mBadgeIcon; 2820 that.mSettingsText = this.mSettingsText; 2821 that.mGroupAlertBehavior = this.mGroupAlertBehavior; 2822 that.mFgsDeferBehavior = this.mFgsDeferBehavior; 2823 that.mBubbleMetadata = this.mBubbleMetadata; 2824 that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; 2825 2826 if (!heavy) { 2827 that.lightenPayload(); // will clean out extras 2828 } 2829 } 2830 visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2831 private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) { 2832 if (icon == null) return; 2833 final int iconType = icon.getType(); 2834 if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) { 2835 visitor.accept(icon.getUri()); 2836 } 2837 } 2838 2839 /** 2840 * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission 2841 * grants will need to be issued to ensure the recipient of this object is able to render its 2842 * contents. 2843 * See b/281044385 for more context and examples about what happens when this isn't done 2844 * correctly. 2845 * 2846 * @hide 2847 */ visitUris(@onNull Consumer<Uri> visitor)2848 public void visitUris(@NonNull Consumer<Uri> visitor) { 2849 if (publicVersion != null) { 2850 publicVersion.visitUris(visitor); 2851 } 2852 2853 visitor.accept(sound); 2854 2855 if (tickerView != null) tickerView.visitUris(visitor); 2856 if (contentView != null) contentView.visitUris(visitor); 2857 if (bigContentView != null) bigContentView.visitUris(visitor); 2858 if (headsUpContentView != null) headsUpContentView.visitUris(visitor); 2859 2860 visitIconUri(visitor, mSmallIcon); 2861 visitIconUri(visitor, mLargeIcon); 2862 2863 if (actions != null) { 2864 for (Action action : actions) { 2865 action.visitUris(visitor); 2866 } 2867 } 2868 2869 if (extras != null) { 2870 visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class)); 2871 visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class)); 2872 2873 // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a 2874 // String representation of a Uri, but the previous implementation (and unit test) of 2875 // this method has always treated it as a Uri object. Given the inconsistency, 2876 // supporting both going forward is the safest choice. 2877 Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI); 2878 if (audioContentsUri instanceof Uri) { 2879 visitor.accept((Uri) audioContentsUri); 2880 } else if (audioContentsUri instanceof String) { 2881 visitor.accept(Uri.parse((String) audioContentsUri)); 2882 } 2883 2884 if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 2885 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); 2886 } 2887 2888 ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class); 2889 if (people != null && !people.isEmpty()) { 2890 for (Person p : people) { 2891 p.visitUris(visitor); 2892 } 2893 } 2894 2895 final RemoteInputHistoryItem[] history = extras.getParcelableArray( 2896 Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 2897 RemoteInputHistoryItem.class); 2898 if (history != null) { 2899 for (int i = 0; i < history.length; i++) { 2900 RemoteInputHistoryItem item = history[i]; 2901 if (item.getUri() != null) { 2902 visitor.accept(item.getUri()); 2903 } 2904 } 2905 } 2906 2907 // Extras for MessagingStyle. We visit them even if not isStyle(MessagingStyle), since 2908 // Notification Listeners might use directly (without the isStyle check). 2909 final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); 2910 if (person != null) { 2911 person.visitUris(visitor); 2912 } 2913 2914 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, 2915 Parcelable.class); 2916 if (!ArrayUtils.isEmpty(messages)) { 2917 for (MessagingStyle.Message message : MessagingStyle.Message 2918 .getMessagesFromBundleArray(messages)) { 2919 message.visitUris(visitor); 2920 } 2921 } 2922 2923 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, 2924 Parcelable.class); 2925 if (!ArrayUtils.isEmpty(historic)) { 2926 for (MessagingStyle.Message message : MessagingStyle.Message 2927 .getMessagesFromBundleArray(historic)) { 2928 message.visitUris(visitor); 2929 } 2930 } 2931 2932 visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class)); 2933 2934 // Extras for CallStyle (same reason for visiting without checking isStyle). 2935 Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); 2936 if (callPerson != null) { 2937 callPerson.visitUris(visitor); 2938 } 2939 visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class)); 2940 } 2941 2942 if (mBubbleMetadata != null) { 2943 visitIconUri(visitor, mBubbleMetadata.getIcon()); 2944 } 2945 2946 if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { 2947 WearableExtender extender = new WearableExtender(this); 2948 extender.visitUris(visitor); 2949 } 2950 } 2951 2952 /** 2953 * Removes heavyweight parts of the Notification object for archival or for sending to 2954 * listeners when the full contents are not necessary. 2955 * @hide 2956 */ lightenPayload()2957 public final void lightenPayload() { 2958 tickerView = null; 2959 contentView = null; 2960 bigContentView = null; 2961 headsUpContentView = null; 2962 mLargeIcon = null; 2963 if (extras != null && !extras.isEmpty()) { 2964 final Set<String> keyset = extras.keySet(); 2965 final int N = keyset.size(); 2966 final String[] keys = keyset.toArray(new String[N]); 2967 for (int i=0; i<N; i++) { 2968 final String key = keys[i]; 2969 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) { 2970 continue; 2971 } 2972 final Object obj = extras.get(key); 2973 if (obj != null && 2974 ( obj instanceof Parcelable 2975 || obj instanceof Parcelable[] 2976 || obj instanceof SparseArray 2977 || obj instanceof ArrayList)) { 2978 extras.remove(key); 2979 } 2980 } 2981 } 2982 } 2983 2984 /** 2985 * Make sure this CharSequence is safe to put into a bundle, which basically 2986 * means it had better not be some custom Parcelable implementation. 2987 * @hide 2988 */ safeCharSequence(CharSequence cs)2989 public static CharSequence safeCharSequence(CharSequence cs) { 2990 if (cs == null) return cs; 2991 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 2992 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 2993 } 2994 if (cs instanceof Parcelable) { 2995 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 2996 + " instance is a custom Parcelable and not allowed in Notification"); 2997 return cs.toString(); 2998 } 2999 return removeTextSizeSpans(cs); 3000 } 3001 removeTextSizeSpans(CharSequence charSequence)3002 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 3003 if (charSequence instanceof Spanned) { 3004 Spanned ss = (Spanned) charSequence; 3005 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 3006 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 3007 for (Object span : spans) { 3008 Object resultSpan = span; 3009 if (resultSpan instanceof CharacterStyle) { 3010 resultSpan = ((CharacterStyle) span).getUnderlying(); 3011 } 3012 if (resultSpan instanceof TextAppearanceSpan) { 3013 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 3014 resultSpan = new TextAppearanceSpan( 3015 originalSpan.getFamily(), 3016 originalSpan.getTextStyle(), 3017 -1, 3018 originalSpan.getTextColor(), 3019 originalSpan.getLinkTextColor()); 3020 } else if (resultSpan instanceof RelativeSizeSpan 3021 || resultSpan instanceof AbsoluteSizeSpan) { 3022 continue; 3023 } else { 3024 resultSpan = span; 3025 } 3026 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 3027 ss.getSpanFlags(span)); 3028 } 3029 return builder; 3030 } 3031 return charSequence; 3032 } 3033 describeContents()3034 public int describeContents() { 3035 return 0; 3036 } 3037 3038 /** 3039 * Flatten this notification into a parcel. 3040 */ writeToParcel(Parcel parcel, int flags)3041 public void writeToParcel(Parcel parcel, int flags) { 3042 // We need to mark all pending intents getting into the notification 3043 // system as being put there to later allow the notification ranker 3044 // to launch them and by doing so add the app to the battery saver white 3045 // list for a short period of time. The problem is that the system 3046 // cannot look into the extras as there may be parcelables there that 3047 // the platform does not know how to handle. To go around that we have 3048 // an explicit list of the pending intents in the extras bundle. 3049 PendingIntent.OnMarshaledListener addedListener = null; 3050 if (allPendingIntents == null) { 3051 addedListener = (PendingIntent intent, Parcel out, int outFlags) -> { 3052 if (parcel == out) { 3053 synchronized (this) { 3054 if (allPendingIntents == null) { 3055 allPendingIntents = new ArraySet<>(); 3056 } 3057 allPendingIntents.add(intent); 3058 } 3059 } 3060 }; 3061 PendingIntent.addOnMarshaledListener(addedListener); 3062 } 3063 try { 3064 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 3065 // want to intercept all pending events written to the parcel. 3066 writeToParcelImpl(parcel, flags); 3067 synchronized (this) { 3068 // Must be written last! 3069 parcel.writeArraySet(allPendingIntents); 3070 } 3071 } finally { 3072 if (addedListener != null) { 3073 PendingIntent.removeOnMarshaledListener(addedListener); 3074 } 3075 } 3076 } 3077 writeToParcelImpl(Parcel parcel, int flags)3078 private void writeToParcelImpl(Parcel parcel, int flags) { 3079 parcel.writeInt(1); 3080 3081 parcel.writeStrongBinder(mAllowlistToken); 3082 parcel.writeLong(when); 3083 parcel.writeLong(creationTime); 3084 if (mSmallIcon == null && icon != 0) { 3085 // you snuck an icon in here without using the builder; let's try to keep it 3086 mSmallIcon = Icon.createWithResource("", icon); 3087 } 3088 if (mSmallIcon != null) { 3089 parcel.writeInt(1); 3090 mSmallIcon.writeToParcel(parcel, 0); 3091 } else { 3092 parcel.writeInt(0); 3093 } 3094 parcel.writeInt(number); 3095 if (contentIntent != null) { 3096 parcel.writeInt(1); 3097 contentIntent.writeToParcel(parcel, 0); 3098 } else { 3099 parcel.writeInt(0); 3100 } 3101 if (deleteIntent != null) { 3102 parcel.writeInt(1); 3103 deleteIntent.writeToParcel(parcel, 0); 3104 } else { 3105 parcel.writeInt(0); 3106 } 3107 if (tickerText != null) { 3108 parcel.writeInt(1); 3109 TextUtils.writeToParcel(tickerText, parcel, flags); 3110 } else { 3111 parcel.writeInt(0); 3112 } 3113 if (tickerView != null) { 3114 parcel.writeInt(1); 3115 tickerView.writeToParcel(parcel, 0); 3116 } else { 3117 parcel.writeInt(0); 3118 } 3119 if (contentView != null) { 3120 parcel.writeInt(1); 3121 contentView.writeToParcel(parcel, 0); 3122 } else { 3123 parcel.writeInt(0); 3124 } 3125 if (mLargeIcon == null && largeIcon != null) { 3126 // you snuck an icon in here without using the builder; let's try to keep it 3127 mLargeIcon = Icon.createWithBitmap(largeIcon); 3128 } 3129 if (mLargeIcon != null) { 3130 parcel.writeInt(1); 3131 mLargeIcon.writeToParcel(parcel, 0); 3132 } else { 3133 parcel.writeInt(0); 3134 } 3135 3136 parcel.writeInt(defaults); 3137 parcel.writeInt(this.flags); 3138 3139 if (sound != null) { 3140 parcel.writeInt(1); 3141 sound.writeToParcel(parcel, 0); 3142 } else { 3143 parcel.writeInt(0); 3144 } 3145 parcel.writeInt(audioStreamType); 3146 3147 if (audioAttributes != null) { 3148 parcel.writeInt(1); 3149 audioAttributes.writeToParcel(parcel, 0); 3150 } else { 3151 parcel.writeInt(0); 3152 } 3153 3154 parcel.writeLongArray(vibrate); 3155 parcel.writeInt(ledARGB); 3156 parcel.writeInt(ledOnMS); 3157 parcel.writeInt(ledOffMS); 3158 parcel.writeInt(iconLevel); 3159 3160 if (fullScreenIntent != null) { 3161 parcel.writeInt(1); 3162 fullScreenIntent.writeToParcel(parcel, 0); 3163 } else { 3164 parcel.writeInt(0); 3165 } 3166 3167 parcel.writeInt(priority); 3168 3169 parcel.writeString8(category); 3170 3171 parcel.writeString8(mGroupKey); 3172 3173 parcel.writeString8(mSortKey); 3174 3175 parcel.writeBundle(extras); // null ok 3176 3177 parcel.writeTypedArray(actions, 0); // null ok 3178 3179 if (bigContentView != null) { 3180 parcel.writeInt(1); 3181 bigContentView.writeToParcel(parcel, 0); 3182 } else { 3183 parcel.writeInt(0); 3184 } 3185 3186 if (headsUpContentView != null) { 3187 parcel.writeInt(1); 3188 headsUpContentView.writeToParcel(parcel, 0); 3189 } else { 3190 parcel.writeInt(0); 3191 } 3192 3193 parcel.writeInt(visibility); 3194 3195 if (publicVersion != null) { 3196 parcel.writeInt(1); 3197 publicVersion.writeToParcel(parcel, 0); 3198 } else { 3199 parcel.writeInt(0); 3200 } 3201 3202 parcel.writeInt(color); 3203 3204 if (mChannelId != null) { 3205 parcel.writeInt(1); 3206 parcel.writeString8(mChannelId); 3207 } else { 3208 parcel.writeInt(0); 3209 } 3210 parcel.writeLong(mTimeout); 3211 3212 if (mShortcutId != null) { 3213 parcel.writeInt(1); 3214 parcel.writeString8(mShortcutId); 3215 } else { 3216 parcel.writeInt(0); 3217 } 3218 3219 if (mLocusId != null) { 3220 parcel.writeInt(1); 3221 mLocusId.writeToParcel(parcel, 0); 3222 } else { 3223 parcel.writeInt(0); 3224 } 3225 3226 parcel.writeInt(mBadgeIcon); 3227 3228 if (mSettingsText != null) { 3229 parcel.writeInt(1); 3230 TextUtils.writeToParcel(mSettingsText, parcel, flags); 3231 } else { 3232 parcel.writeInt(0); 3233 } 3234 3235 parcel.writeInt(mGroupAlertBehavior); 3236 3237 if (mBubbleMetadata != null) { 3238 parcel.writeInt(1); 3239 mBubbleMetadata.writeToParcel(parcel, 0); 3240 } else { 3241 parcel.writeInt(0); 3242 } 3243 3244 parcel.writeBoolean(mAllowSystemGeneratedContextualActions); 3245 3246 parcel.writeInt(mFgsDeferBehavior); 3247 3248 // mUsesStandardHeader is not written because it should be recomputed in listeners 3249 } 3250 3251 /** 3252 * Parcelable.Creator that instantiates Notification objects 3253 */ 3254 public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR 3255 = new Parcelable.Creator<Notification>() 3256 { 3257 public Notification createFromParcel(Parcel parcel) 3258 { 3259 return new Notification(parcel); 3260 } 3261 3262 public Notification[] newArray(int size) 3263 { 3264 return new Notification[size]; 3265 } 3266 }; 3267 3268 /** 3269 * @hide 3270 */ areActionsVisiblyDifferent(Notification first, Notification second)3271 public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { 3272 Notification.Action[] firstAs = first.actions; 3273 Notification.Action[] secondAs = second.actions; 3274 if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { 3275 return true; 3276 } 3277 if (firstAs != null && secondAs != null) { 3278 if (firstAs.length != secondAs.length) { 3279 return true; 3280 } 3281 for (int i = 0; i < firstAs.length; i++) { 3282 if (!Objects.equals(String.valueOf(firstAs[i].title), 3283 String.valueOf(secondAs[i].title))) { 3284 return true; 3285 } 3286 RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); 3287 RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); 3288 if (firstRs == null) { 3289 firstRs = new RemoteInput[0]; 3290 } 3291 if (secondRs == null) { 3292 secondRs = new RemoteInput[0]; 3293 } 3294 if (firstRs.length != secondRs.length) { 3295 return true; 3296 } 3297 for (int j = 0; j < firstRs.length; j++) { 3298 if (!Objects.equals(String.valueOf(firstRs[j].getLabel()), 3299 String.valueOf(secondRs[j].getLabel()))) { 3300 return true; 3301 } 3302 } 3303 } 3304 } 3305 return false; 3306 } 3307 3308 /** 3309 * @hide 3310 */ areIconsDifferent(Notification first, Notification second)3311 public static boolean areIconsDifferent(Notification first, Notification second) { 3312 return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon()) 3313 || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon()); 3314 } 3315 3316 /** 3317 * Note that we aren't actually comparing the contents of the bitmaps here; this is only a 3318 * cursory inspection. We will not return false negatives, but false positives are likely. 3319 */ areIconsMaybeDifferent(Icon a, Icon b)3320 private static boolean areIconsMaybeDifferent(Icon a, Icon b) { 3321 if (a == b) { 3322 return false; 3323 } 3324 if (a == null || b == null) { 3325 return true; 3326 } 3327 if (a.sameAs(b)) { 3328 return false; 3329 } 3330 final int aType = a.getType(); 3331 if (aType != b.getType()) { 3332 return true; 3333 } 3334 if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) { 3335 final Bitmap aBitmap = a.getBitmap(); 3336 final Bitmap bBitmap = b.getBitmap(); 3337 return aBitmap.getWidth() != bBitmap.getWidth() 3338 || aBitmap.getHeight() != bBitmap.getHeight() 3339 || aBitmap.getConfig() != bBitmap.getConfig() 3340 || aBitmap.getGenerationId() != bBitmap.getGenerationId(); 3341 } 3342 return true; 3343 } 3344 3345 /** 3346 * @hide 3347 */ areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3348 public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { 3349 if (first.getStyle() == null) { 3350 return second.getStyle() != null; 3351 } 3352 if (second.getStyle() == null) { 3353 return true; 3354 } 3355 return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); 3356 } 3357 3358 /** 3359 * @hide 3360 */ areRemoteViewsChanged(Builder first, Builder second)3361 public static boolean areRemoteViewsChanged(Builder first, Builder second) { 3362 if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) { 3363 return true; 3364 } 3365 3366 if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) { 3367 return true; 3368 } 3369 if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) { 3370 return true; 3371 } 3372 if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) { 3373 return true; 3374 } 3375 3376 return false; 3377 } 3378 areRemoteViewsChanged(RemoteViews first, RemoteViews second)3379 private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) { 3380 if (first == null && second == null) { 3381 return false; 3382 } 3383 if (first == null && second != null || first != null && second == null) { 3384 return true; 3385 } 3386 3387 if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) { 3388 return true; 3389 } 3390 3391 if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) { 3392 return true; 3393 } 3394 3395 return false; 3396 } 3397 3398 /** 3399 * Parcelling creates multiple copies of objects in {@code extras}. Fix them. 3400 * <p> 3401 * For backwards compatibility {@code extras} holds some references to "real" member data such 3402 * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly 3403 * fine as long as the object stays in one process. 3404 * <p> 3405 * However, once the notification goes into a parcel each reference gets marshalled separately, 3406 * wasting memory. Especially with large images on Auto and TV, this is worth fixing. 3407 */ fixDuplicateExtras()3408 private void fixDuplicateExtras() { 3409 if (extras != null) { 3410 fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); 3411 } 3412 } 3413 3414 /** 3415 * If we find an extra that's exactly the same as one of the "real" fields but refers to a 3416 * separate object, replace it with the field's version to avoid holding duplicate copies. 3417 */ fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3418 private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { 3419 if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) { 3420 extras.putParcelable(extraName, original); 3421 } 3422 } 3423 3424 /** 3425 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 3426 * layout. 3427 * 3428 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 3429 * in the view.</p> 3430 * @param context The context for your application / activity. 3431 * @param contentTitle The title that goes in the expanded entry. 3432 * @param contentText The text that goes in the expanded entry. 3433 * @param contentIntent The intent to launch when the user clicks the expanded notification. 3434 * If this is an activity, it must include the 3435 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 3436 * that you take care of task management as described in the 3437 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 3438 * Stack</a> document. 3439 * 3440 * @deprecated Use {@link Builder} instead. 3441 * @removed 3442 */ 3443 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3444 public void setLatestEventInfo(Context context, 3445 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 3446 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 3447 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 3448 new Throwable()); 3449 } 3450 3451 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3452 extras.putBoolean(EXTRA_SHOW_WHEN, true); 3453 } 3454 3455 // ensure that any information already set directly is preserved 3456 final Notification.Builder builder = new Notification.Builder(context, this); 3457 3458 // now apply the latestEventInfo fields 3459 if (contentTitle != null) { 3460 builder.setContentTitle(contentTitle); 3461 } 3462 if (contentText != null) { 3463 builder.setContentText(contentText); 3464 } 3465 builder.setContentIntent(contentIntent); 3466 3467 builder.build(); // callers expect this notification to be ready to use 3468 } 3469 3470 /** 3471 * Sets the token used for background operations for the pending intents associated with this 3472 * notification. 3473 * 3474 * This token is automatically set during deserialization for you, you usually won't need to 3475 * call this unless you want to change the existing token, if any. 3476 * 3477 * @hide 3478 */ clearAllowlistToken()3479 public void clearAllowlistToken() { 3480 mAllowlistToken = null; 3481 if (publicVersion != null) { 3482 publicVersion.clearAllowlistToken(); 3483 } 3484 } 3485 3486 /** 3487 * @hide 3488 */ addFieldsFromContext(Context context, Notification notification)3489 public static void addFieldsFromContext(Context context, Notification notification) { 3490 addFieldsFromContext(context.getApplicationInfo(), notification); 3491 } 3492 3493 /** 3494 * @hide 3495 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)3496 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 3497 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 3498 } 3499 3500 /** 3501 * @hide 3502 */ dumpDebug(ProtoOutputStream proto, long fieldId)3503 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 3504 long token = proto.start(fieldId); 3505 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 3506 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 3507 proto.write(NotificationProto.FLAGS, this.flags); 3508 proto.write(NotificationProto.COLOR, this.color); 3509 proto.write(NotificationProto.CATEGORY, this.category); 3510 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 3511 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 3512 if (this.actions != null) { 3513 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 3514 } 3515 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 3516 proto.write(NotificationProto.VISIBILITY, this.visibility); 3517 } 3518 if (publicVersion != null) { 3519 publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION); 3520 } 3521 proto.end(token); 3522 } 3523 3524 @Override toString()3525 public String toString() { 3526 StringBuilder sb = new StringBuilder(); 3527 sb.append("Notification(channel="); 3528 sb.append(getChannelId()); 3529 sb.append(" shortcut="); 3530 sb.append(getShortcutId()); 3531 sb.append(" contentView="); 3532 if (contentView != null) { 3533 sb.append(contentView.getPackage()); 3534 sb.append("/0x"); 3535 sb.append(Integer.toHexString(contentView.getLayoutId())); 3536 } else { 3537 sb.append("null"); 3538 } 3539 sb.append(" vibrate="); 3540 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3541 sb.append("default"); 3542 } else if (this.vibrate != null) { 3543 int N = this.vibrate.length-1; 3544 sb.append("["); 3545 for (int i=0; i<N; i++) { 3546 sb.append(this.vibrate[i]); 3547 sb.append(','); 3548 } 3549 if (N != -1) { 3550 sb.append(this.vibrate[N]); 3551 } 3552 sb.append("]"); 3553 } else { 3554 sb.append("null"); 3555 } 3556 sb.append(" sound="); 3557 if ((this.defaults & DEFAULT_SOUND) != 0) { 3558 sb.append("default"); 3559 } else if (this.sound != null) { 3560 sb.append(this.sound.toString()); 3561 } else { 3562 sb.append("null"); 3563 } 3564 if (this.tickerText != null) { 3565 sb.append(" tick"); 3566 } 3567 sb.append(" defaults=0x"); 3568 sb.append(Integer.toHexString(this.defaults)); 3569 sb.append(" flags=0x"); 3570 sb.append(Integer.toHexString(this.flags)); 3571 sb.append(String.format(" color=0x%08x", this.color)); 3572 if (this.category != null) { 3573 sb.append(" category="); 3574 sb.append(this.category); 3575 } 3576 if (this.mGroupKey != null) { 3577 sb.append(" groupKey="); 3578 sb.append(this.mGroupKey); 3579 } 3580 if (this.mSortKey != null) { 3581 sb.append(" sortKey="); 3582 sb.append(this.mSortKey); 3583 } 3584 if (actions != null) { 3585 sb.append(" actions="); 3586 sb.append(actions.length); 3587 } 3588 sb.append(" vis="); 3589 sb.append(visibilityToString(this.visibility)); 3590 if (this.publicVersion != null) { 3591 sb.append(" publicVersion="); 3592 sb.append(publicVersion.toString()); 3593 } 3594 if (this.mLocusId != null) { 3595 sb.append(" locusId="); 3596 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3597 } 3598 sb.append(")"); 3599 return sb.toString(); 3600 } 3601 3602 /** 3603 * {@hide} 3604 */ visibilityToString(int vis)3605 public static String visibilityToString(int vis) { 3606 switch (vis) { 3607 case VISIBILITY_PRIVATE: 3608 return "PRIVATE"; 3609 case VISIBILITY_PUBLIC: 3610 return "PUBLIC"; 3611 case VISIBILITY_SECRET: 3612 return "SECRET"; 3613 default: 3614 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3615 } 3616 } 3617 3618 /** 3619 * {@hide} 3620 */ priorityToString(@riority int pri)3621 public static String priorityToString(@Priority int pri) { 3622 switch (pri) { 3623 case PRIORITY_MIN: 3624 return "MIN"; 3625 case PRIORITY_LOW: 3626 return "LOW"; 3627 case PRIORITY_DEFAULT: 3628 return "DEFAULT"; 3629 case PRIORITY_HIGH: 3630 return "HIGH"; 3631 case PRIORITY_MAX: 3632 return "MAX"; 3633 default: 3634 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3635 } 3636 } 3637 3638 /** 3639 * @hide 3640 */ hasCompletedProgress()3641 public boolean hasCompletedProgress() { 3642 // not a progress notification; can't be complete 3643 if (!extras.containsKey(EXTRA_PROGRESS) 3644 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 3645 return false; 3646 } 3647 // many apps use max 0 for 'indeterminate'; not complete 3648 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 3649 return false; 3650 } 3651 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 3652 } 3653 3654 /** @removed */ 3655 @Deprecated getChannel()3656 public String getChannel() { 3657 return mChannelId; 3658 } 3659 3660 /** 3661 * Returns the id of the channel this notification posts to. 3662 */ getChannelId()3663 public String getChannelId() { 3664 return mChannelId; 3665 } 3666 3667 /** @removed */ 3668 @Deprecated getTimeout()3669 public long getTimeout() { 3670 return mTimeout; 3671 } 3672 3673 /** 3674 * Returns the duration from posting after which this notification should be canceled by the 3675 * system, if it's not canceled already. 3676 */ getTimeoutAfter()3677 public long getTimeoutAfter() { 3678 return mTimeout; 3679 } 3680 3681 /** 3682 * Returns what icon should be shown for this notification if it is being displayed in a 3683 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 3684 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 3685 */ getBadgeIconType()3686 public int getBadgeIconType() { 3687 return mBadgeIcon; 3688 } 3689 3690 /** 3691 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 3692 * 3693 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 3694 * notifications. 3695 */ getShortcutId()3696 public String getShortcutId() { 3697 return mShortcutId; 3698 } 3699 3700 /** 3701 * Gets the {@link LocusId} associated with this notification. 3702 * 3703 * <p>Used by the device's intelligence services to correlate objects (such as 3704 * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated. 3705 */ 3706 @Nullable getLocusId()3707 public LocusId getLocusId() { 3708 return mLocusId; 3709 } 3710 3711 /** 3712 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 3713 */ getSettingsText()3714 public CharSequence getSettingsText() { 3715 return mSettingsText; 3716 } 3717 3718 /** 3719 * Returns which type of notifications in a group are responsible for audibly alerting the 3720 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 3721 * {@link #GROUP_ALERT_SUMMARY}. 3722 */ getGroupAlertBehavior()3723 public @GroupAlertBehavior int getGroupAlertBehavior() { 3724 return mGroupAlertBehavior; 3725 } 3726 3727 /** 3728 * Returns the bubble metadata that will be used to display app content in a floating window 3729 * over the existing foreground activity. 3730 */ 3731 @Nullable getBubbleMetadata()3732 public BubbleMetadata getBubbleMetadata() { 3733 return mBubbleMetadata; 3734 } 3735 3736 /** 3737 * Sets the {@link BubbleMetadata} for this notification. 3738 * @hide 3739 */ setBubbleMetadata(BubbleMetadata data)3740 public void setBubbleMetadata(BubbleMetadata data) { 3741 mBubbleMetadata = data; 3742 } 3743 3744 /** 3745 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 3746 * for this notification. 3747 */ getAllowSystemGeneratedContextualActions()3748 public boolean getAllowSystemGeneratedContextualActions() { 3749 return mAllowSystemGeneratedContextualActions; 3750 } 3751 3752 /** 3753 * The small icon representing this notification in the status bar and content view. 3754 * 3755 * @return the small icon representing this notification. 3756 * 3757 * @see Builder#getSmallIcon() 3758 * @see Builder#setSmallIcon(Icon) 3759 */ getSmallIcon()3760 public Icon getSmallIcon() { 3761 return mSmallIcon; 3762 } 3763 3764 /** 3765 * Used when notifying to clean up legacy small icons. 3766 * @hide 3767 */ 3768 @UnsupportedAppUsage setSmallIcon(Icon icon)3769 public void setSmallIcon(Icon icon) { 3770 mSmallIcon = icon; 3771 } 3772 3773 /** 3774 * The large icon shown in this notification's content view. 3775 * @see Builder#getLargeIcon() 3776 * @see Builder#setLargeIcon(Icon) 3777 */ getLargeIcon()3778 public Icon getLargeIcon() { 3779 return mLargeIcon; 3780 } 3781 3782 /** 3783 * @hide 3784 */ 3785 @UnsupportedAppUsage isGroupSummary()3786 public boolean isGroupSummary() { 3787 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 3788 } 3789 3790 /** 3791 * @hide 3792 */ 3793 @UnsupportedAppUsage isGroupChild()3794 public boolean isGroupChild() { 3795 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 3796 } 3797 3798 /** 3799 * @hide 3800 */ suppressAlertingDueToGrouping()3801 public boolean suppressAlertingDueToGrouping() { 3802 if (isGroupSummary() 3803 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 3804 return true; 3805 } else if (isGroupChild() 3806 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 3807 return true; 3808 } 3809 return false; 3810 } 3811 3812 3813 /** 3814 * Finds and returns a remote input and its corresponding action. 3815 * 3816 * @param requiresFreeform requires the remoteinput to allow freeform or not. 3817 * @return the result pair, {@code null} if no result is found. 3818 */ 3819 @Nullable findRemoteInputActionPair(boolean requiresFreeform)3820 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 3821 if (actions == null) { 3822 return null; 3823 } 3824 for (Notification.Action action : actions) { 3825 if (action.getRemoteInputs() == null) { 3826 continue; 3827 } 3828 RemoteInput resultRemoteInput = null; 3829 for (RemoteInput remoteInput : action.getRemoteInputs()) { 3830 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 3831 resultRemoteInput = remoteInput; 3832 } 3833 } 3834 if (resultRemoteInput != null) { 3835 return Pair.create(resultRemoteInput, action); 3836 } 3837 } 3838 return null; 3839 } 3840 3841 /** 3842 * Returns the actions that are contextual (that is, suggested because of the content of the 3843 * notification) out of the actions in this notification. 3844 */ getContextualActions()3845 public @NonNull List<Notification.Action> getContextualActions() { 3846 if (actions == null) return Collections.emptyList(); 3847 3848 List<Notification.Action> contextualActions = new ArrayList<>(); 3849 for (Notification.Action action : actions) { 3850 if (action.isContextual()) { 3851 contextualActions.add(action); 3852 } 3853 } 3854 return contextualActions; 3855 } 3856 3857 /** 3858 * Builder class for {@link Notification} objects. 3859 * 3860 * Provides a convenient way to set the various fields of a {@link Notification} and generate 3861 * content views using the platform's notification layout template. If your app supports 3862 * versions of Android as old as API level 4, you can instead use 3863 * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder}, 3864 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 3865 * library</a>. 3866 * 3867 * <p>Example: 3868 * 3869 * <pre class="prettyprint"> 3870 * Notification noti = new Notification.Builder(mContext) 3871 * .setContentTitle("New mail from " + sender.toString()) 3872 * .setContentText(subject) 3873 * .setSmallIcon(R.drawable.new_mail) 3874 * .setLargeIcon(aBitmap) 3875 * .build(); 3876 * </pre> 3877 */ 3878 public static class Builder { 3879 /** 3880 * @hide 3881 */ 3882 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 3883 "android.rebuild.contentViewActionCount"; 3884 /** 3885 * @hide 3886 */ 3887 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 3888 = "android.rebuild.bigViewActionCount"; 3889 /** 3890 * @hide 3891 */ 3892 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 3893 = "android.rebuild.hudViewActionCount"; 3894 3895 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 3896 SystemProperties.getBoolean("notifications.only_title", true); 3897 3898 /** 3899 * The lightness difference that has to be added to the primary text color to obtain the 3900 * secondary text color when the background is light. 3901 */ 3902 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 3903 3904 /** 3905 * The lightness difference that has to be added to the primary text color to obtain the 3906 * secondary text color when the background is dark. 3907 * A bit less then the above value, since it looks better on dark backgrounds. 3908 */ 3909 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 3910 3911 private Context mContext; 3912 private Notification mN; 3913 private Bundle mUserExtras = new Bundle(); 3914 private Style mStyle; 3915 @UnsupportedAppUsage 3916 private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS); 3917 private ArrayList<Person> mPersonList = new ArrayList<>(); 3918 private ContrastColorUtil mColorUtil; 3919 private boolean mIsLegacy; 3920 private boolean mIsLegacyInitialized; 3921 3922 /** 3923 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 3924 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 3925 */ 3926 StandardTemplateParams mParams = new StandardTemplateParams(); 3927 Colors mColors = new Colors(); 3928 3929 private boolean mTintActionButtons; 3930 private boolean mInNightMode; 3931 3932 /** 3933 * Constructs a new Builder with the defaults: 3934 * 3935 * @param context 3936 * A {@link Context} that will be used by the Builder to construct the 3937 * RemoteViews. The Context will not be held past the lifetime of this Builder 3938 * object. 3939 * @param channelId 3940 * The constructed Notification will be posted on this 3941 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 3942 * created using {@link NotificationManager#createNotificationChannel}. 3943 */ Builder(Context context, String channelId)3944 public Builder(Context context, String channelId) { 3945 this(context, (Notification) null); 3946 mN.mChannelId = channelId; 3947 } 3948 3949 /** 3950 * @deprecated use {@link #Builder(Context, String)} 3951 * instead. All posted Notifications must specify a NotificationChannel Id. 3952 */ 3953 @Deprecated Builder(Context context)3954 public Builder(Context context) { 3955 this(context, (Notification) null); 3956 } 3957 3958 /** 3959 * @hide 3960 */ Builder(Context context, Notification toAdopt)3961 public Builder(Context context, Notification toAdopt) { 3962 mContext = context; 3963 Resources res = mContext.getResources(); 3964 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 3965 3966 if (res.getBoolean(R.bool.config_enableNightMode)) { 3967 Configuration currentConfig = res.getConfiguration(); 3968 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 3969 == Configuration.UI_MODE_NIGHT_YES; 3970 } 3971 3972 if (toAdopt == null) { 3973 mN = new Notification(); 3974 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3975 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 3976 } 3977 mN.priority = PRIORITY_DEFAULT; 3978 mN.visibility = VISIBILITY_PRIVATE; 3979 } else { 3980 mN = toAdopt; 3981 if (mN.actions != null) { 3982 Collections.addAll(mActions, mN.actions); 3983 } 3984 3985 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 3986 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, 3987 android.app.Person.class); 3988 if (people != null && !people.isEmpty()) { 3989 mPersonList.addAll(people); 3990 } 3991 } 3992 3993 if (mN.getSmallIcon() == null && mN.icon != 0) { 3994 setSmallIcon(mN.icon); 3995 } 3996 3997 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 3998 setLargeIcon(mN.largeIcon); 3999 } 4000 4001 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 4002 if (!TextUtils.isEmpty(templateClass)) { 4003 final Class<? extends Style> styleClass 4004 = getNotificationStyleClass(templateClass); 4005 if (styleClass == null) { 4006 Log.d(TAG, "Unknown style class: " + templateClass); 4007 } else { 4008 try { 4009 final Constructor<? extends Style> ctor = 4010 styleClass.getDeclaredConstructor(); 4011 ctor.setAccessible(true); 4012 final Style style = ctor.newInstance(); 4013 style.restoreFromExtras(mN.extras); 4014 4015 if (style != null) { 4016 setStyle(style); 4017 } 4018 } catch (Throwable t) { 4019 Log.e(TAG, "Could not create Style", t); 4020 } 4021 } 4022 } 4023 } 4024 } 4025 getColorUtil()4026 private ContrastColorUtil getColorUtil() { 4027 if (mColorUtil == null) { 4028 mColorUtil = ContrastColorUtil.getInstance(mContext); 4029 } 4030 return mColorUtil; 4031 } 4032 4033 /** 4034 * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that 4035 * use this method to link to a published long-lived sharing shortcut may appear in a 4036 * dedicated Conversation section of the shade and may show configuration options that 4037 * are unique to conversations. This behavior should be reserved for person to person(s) 4038 * conversations where there is a likely social obligation for an individual to respond. 4039 * <p> 4040 * For example, the following are some examples of notifications that belong in the 4041 * conversation space: 4042 * <ul> 4043 * <li>1:1 conversations between two individuals</li> 4044 * <li>Group conversations between individuals where everyone can contribute</li> 4045 * </ul> 4046 * And the following are some examples of notifications that do not belong in the 4047 * conversation space: 4048 * <ul> 4049 * <li>Advertisements from a bot (even if personal and contextualized)</li> 4050 * <li>Engagement notifications from a bot</li> 4051 * <li>Directional conversations where there is an active speaker and many passive 4052 * individuals</li> 4053 * <li>Stream / posting updates from other individuals</li> 4054 * <li>Email, document comments, or other conversation types that are not real-time</li> 4055 * </ul> 4056 * </p> 4057 * 4058 * <p> 4059 * Additionally, this method can be used for all types of notifications to mark this 4060 * notification as duplicative of a Launcher shortcut. Launchers that show badges or 4061 * notification content may then suppress the shortcut in favor of the content of this 4062 * notification. 4063 * <p> 4064 * If this notification has {@link BubbleMetadata} attached that was created with 4065 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 4066 * metadata matches the shortcutId set here, if one was set. If the shortcutId's were 4067 * specified but do not match, an exception is thrown. 4068 * 4069 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 4070 * is linked to 4071 * 4072 * @see BubbleMetadata.Builder#Builder(String) 4073 */ 4074 @NonNull setShortcutId(String shortcutId)4075 public Builder setShortcutId(String shortcutId) { 4076 mN.mShortcutId = shortcutId; 4077 return this; 4078 } 4079 4080 /** 4081 * Sets the {@link LocusId} associated with this notification. 4082 * 4083 * <p>This method should be called when the {@link LocusId} is used in other places (such 4084 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence 4085 * services can correlate them. 4086 */ 4087 @NonNull setLocusId(@ullable LocusId locusId)4088 public Builder setLocusId(@Nullable LocusId locusId) { 4089 mN.mLocusId = locusId; 4090 return this; 4091 } 4092 4093 /** 4094 * Sets which icon to display as a badge for this notification. 4095 * 4096 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 4097 * {@link #BADGE_ICON_LARGE}. 4098 * 4099 * Note: This value might be ignored, for launchers that don't support badge icons. 4100 */ 4101 @NonNull setBadgeIconType(int icon)4102 public Builder setBadgeIconType(int icon) { 4103 mN.mBadgeIcon = icon; 4104 return this; 4105 } 4106 4107 /** 4108 * Sets the group alert behavior for this notification. Use this method to mute this 4109 * notification if alerts for this notification's group should be handled by a different 4110 * notification. This is only applicable for notifications that belong to a 4111 * {@link #setGroup(String) group}. This must be called on all notifications you want to 4112 * mute. For example, if you want only the summary of your group to make noise and/or peek 4113 * on screen, all children in the group should have the group alert behavior 4114 * {@link #GROUP_ALERT_SUMMARY}. 4115 * 4116 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 4117 */ 4118 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4119 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 4120 mN.mGroupAlertBehavior = groupAlertBehavior; 4121 return this; 4122 } 4123 4124 /** 4125 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 4126 * window over the existing foreground activity. 4127 * 4128 * <p>This data will be ignored unless the notification is posted to a channel that 4129 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 4130 * 4131 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 4132 * collapsed state outside of the notification shade on unlocked devices. When a user 4133 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 4134 */ 4135 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)4136 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 4137 mN.mBubbleMetadata = data; 4138 return this; 4139 } 4140 4141 /** @removed */ 4142 @Deprecated setChannel(String channelId)4143 public Builder setChannel(String channelId) { 4144 mN.mChannelId = channelId; 4145 return this; 4146 } 4147 4148 /** 4149 * Specifies the channel the notification should be delivered on. 4150 */ 4151 @NonNull setChannelId(String channelId)4152 public Builder setChannelId(String channelId) { 4153 mN.mChannelId = channelId; 4154 return this; 4155 } 4156 4157 /** @removed */ 4158 @Deprecated setTimeout(long durationMs)4159 public Builder setTimeout(long durationMs) { 4160 mN.mTimeout = durationMs; 4161 return this; 4162 } 4163 4164 /** 4165 * Specifies a duration in milliseconds after which this notification should be canceled, 4166 * if it is not already canceled. 4167 */ 4168 @NonNull setTimeoutAfter(long durationMs)4169 public Builder setTimeoutAfter(long durationMs) { 4170 mN.mTimeout = durationMs; 4171 return this; 4172 } 4173 4174 /** 4175 * Add a timestamp pertaining to the notification (usually the time the event occurred). 4176 * 4177 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 4178 * shown anymore by default and must be opted into by using 4179 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 4180 * 4181 * @see Notification#when 4182 */ 4183 @NonNull setWhen(long when)4184 public Builder setWhen(long when) { 4185 mN.when = when; 4186 return this; 4187 } 4188 4189 /** 4190 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 4191 * in the content view. 4192 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 4193 * {@code false}. For earlier apps, the default is {@code true}. 4194 */ 4195 @NonNull setShowWhen(boolean show)4196 public Builder setShowWhen(boolean show) { 4197 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 4198 return this; 4199 } 4200 4201 /** 4202 * Show the {@link Notification#when} field as a stopwatch. 4203 * 4204 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 4205 * automatically updating display of the minutes and seconds since <code>when</code>. 4206 * 4207 * Useful when showing an elapsed time (like an ongoing phone call). 4208 * 4209 * The counter can also be set to count down to <code>when</code> when using 4210 * {@link #setChronometerCountDown(boolean)}. 4211 * 4212 * @see android.widget.Chronometer 4213 * @see Notification#when 4214 * @see #setChronometerCountDown(boolean) 4215 */ 4216 @NonNull setUsesChronometer(boolean b)4217 public Builder setUsesChronometer(boolean b) { 4218 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 4219 return this; 4220 } 4221 4222 /** 4223 * Sets the Chronometer to count down instead of counting up. 4224 * 4225 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 4226 * If it isn't set the chronometer will count up. 4227 * 4228 * @see #setUsesChronometer(boolean) 4229 */ 4230 @NonNull setChronometerCountDown(boolean countDown)4231 public Builder setChronometerCountDown(boolean countDown) { 4232 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 4233 return this; 4234 } 4235 4236 /** 4237 * Set the small icon resource, which will be used to represent the notification in the 4238 * status bar. 4239 * 4240 4241 * The platform template for the expanded view will draw this icon in the left, unless a 4242 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 4243 * icon will be moved to the right-hand side. 4244 * 4245 4246 * @param icon 4247 * A resource ID in the application's package of the drawable to use. 4248 * @see Notification#icon 4249 */ 4250 @NonNull setSmallIcon(@rawableRes int icon)4251 public Builder setSmallIcon(@DrawableRes int icon) { 4252 return setSmallIcon(icon != 0 4253 ? Icon.createWithResource(mContext, icon) 4254 : null); 4255 } 4256 4257 /** 4258 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 4259 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 4260 * LevelListDrawable}. 4261 * 4262 * @param icon A resource ID in the application's package of the drawable to use. 4263 * @param level The level to use for the icon. 4264 * 4265 * @see Notification#icon 4266 * @see Notification#iconLevel 4267 */ 4268 @NonNull setSmallIcon(@rawableRes int icon, int level)4269 public Builder setSmallIcon(@DrawableRes int icon, int level) { 4270 mN.iconLevel = level; 4271 return setSmallIcon(icon); 4272 } 4273 4274 /** 4275 * Set the small icon, which will be used to represent the notification in the 4276 * status bar and content view (unless overridden there by a 4277 * {@link #setLargeIcon(Bitmap) large icon}). 4278 * 4279 * @param icon An Icon object to use. 4280 * @see Notification#icon 4281 */ 4282 @NonNull setSmallIcon(Icon icon)4283 public Builder setSmallIcon(Icon icon) { 4284 mN.setSmallIcon(icon); 4285 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 4286 mN.icon = icon.getResId(); 4287 } 4288 return this; 4289 } 4290 4291 /** 4292 * Set the first line of text in the platform notification template. 4293 */ 4294 @NonNull setContentTitle(CharSequence title)4295 public Builder setContentTitle(CharSequence title) { 4296 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 4297 return this; 4298 } 4299 4300 /** 4301 * Set the second line of text in the platform notification template. 4302 */ 4303 @NonNull setContentText(CharSequence text)4304 public Builder setContentText(CharSequence text) { 4305 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 4306 return this; 4307 } 4308 4309 /** 4310 * This provides some additional information that is displayed in the notification. No 4311 * guarantees are given where exactly it is displayed. 4312 * 4313 * <p>This information should only be provided if it provides an essential 4314 * benefit to the understanding of the notification. The more text you provide the 4315 * less readable it becomes. For example, an email client should only provide the account 4316 * name here if more than one email account has been added.</p> 4317 * 4318 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 4319 * notification header area. 4320 * 4321 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 4322 * this will be shown in the third line of text in the platform notification template. 4323 * You should not be using {@link #setProgress(int, int, boolean)} at the 4324 * same time on those versions; they occupy the same place. 4325 * </p> 4326 */ 4327 @NonNull setSubText(CharSequence text)4328 public Builder setSubText(CharSequence text) { 4329 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 4330 return this; 4331 } 4332 4333 /** 4334 * Provides text that will appear as a link to your application's settings. 4335 * 4336 * <p>This text does not appear within notification {@link Style templates} but may 4337 * appear when the user uses an affordance to learn more about the notification. 4338 * Additionally, this text will not appear unless you provide a valid link target by 4339 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 4340 * 4341 * <p>This text is meant to be concise description about what the user can customize 4342 * when they click on this link. The recommended maximum length is 40 characters. 4343 * @param text 4344 * @return 4345 */ 4346 @NonNull setSettingsText(CharSequence text)4347 public Builder setSettingsText(CharSequence text) { 4348 mN.mSettingsText = safeCharSequence(text); 4349 return this; 4350 } 4351 4352 /** 4353 * Set the remote input history. 4354 * 4355 * This should be set to the most recent inputs that have been sent 4356 * through a {@link RemoteInput} of this Notification and cleared once the it is no 4357 * longer relevant (e.g. for chat notifications once the other party has responded). 4358 * 4359 * The most recent input must be stored at the 0 index, the second most recent at the 4360 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 4361 * and how much of each individual input is shown. 4362 * 4363 * <p>Note: The reply text will only be shown on notifications that have least one action 4364 * with a {@code RemoteInput}.</p> 4365 */ 4366 @NonNull setRemoteInputHistory(CharSequence[] text)4367 public Builder setRemoteInputHistory(CharSequence[] text) { 4368 if (text == null) { 4369 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 4370 } else { 4371 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); 4372 CharSequence[] safe = new CharSequence[itemCount]; 4373 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; 4374 for (int i = 0; i < itemCount; i++) { 4375 safe[i] = safeCharSequence(text[i]); 4376 items[i] = new RemoteInputHistoryItem(text[i]); 4377 } 4378 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 4379 4380 // Also add these messages as structured history items. 4381 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); 4382 } 4383 return this; 4384 } 4385 4386 /** 4387 * Set the remote input history, with support for embedding URIs and mime types for 4388 * images and other media. 4389 * @hide 4390 */ 4391 @NonNull setRemoteInputHistory(RemoteInputHistoryItem[] items)4392 public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { 4393 if (items == null) { 4394 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); 4395 } else { 4396 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); 4397 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; 4398 for (int i = 0; i < itemCount; i++) { 4399 history[i] = items[i]; 4400 } 4401 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); 4402 } 4403 return this; 4404 } 4405 4406 /** 4407 * Sets whether remote history entries view should have a spinner. 4408 * @hide 4409 */ 4410 @NonNull setShowRemoteInputSpinner(boolean showSpinner)4411 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 4412 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 4413 return this; 4414 } 4415 4416 /** 4417 * Sets whether smart reply buttons should be hidden. 4418 * @hide 4419 */ 4420 @NonNull setHideSmartReplies(boolean hideSmartReplies)4421 public Builder setHideSmartReplies(boolean hideSmartReplies) { 4422 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 4423 return this; 4424 } 4425 4426 /** 4427 * Sets the number of items this notification represents. May be displayed as a badge count 4428 * for Launchers that support badging. 4429 */ 4430 @NonNull setNumber(int number)4431 public Builder setNumber(int number) { 4432 mN.number = number; 4433 return this; 4434 } 4435 4436 /** 4437 * A small piece of additional information pertaining to this notification. 4438 * 4439 * The platform template will draw this on the last line of the notification, at the far 4440 * right (to the right of a smallIcon if it has been placed there). 4441 * 4442 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 4443 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 4444 * field will still show up, but the subtext will take precedence. 4445 */ 4446 @Deprecated setContentInfo(CharSequence info)4447 public Builder setContentInfo(CharSequence info) { 4448 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 4449 return this; 4450 } 4451 4452 /** 4453 * Set the progress this notification represents. 4454 * 4455 * The platform template will represent this using a {@link ProgressBar}. 4456 */ 4457 @NonNull setProgress(int max, int progress, boolean indeterminate)4458 public Builder setProgress(int max, int progress, boolean indeterminate) { 4459 mN.extras.putInt(EXTRA_PROGRESS, progress); 4460 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 4461 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 4462 return this; 4463 } 4464 4465 /** 4466 * Supply a custom RemoteViews to use instead of the platform template. 4467 * 4468 * Use {@link #setCustomContentView(RemoteViews)} instead. 4469 */ 4470 @Deprecated setContent(RemoteViews views)4471 public Builder setContent(RemoteViews views) { 4472 return setCustomContentView(views); 4473 } 4474 4475 /** 4476 * Supply custom RemoteViews to use instead of the platform template. 4477 * 4478 * This will override the layout that would otherwise be constructed by this Builder 4479 * object. 4480 */ 4481 @NonNull setCustomContentView(RemoteViews contentView)4482 public Builder setCustomContentView(RemoteViews contentView) { 4483 mN.contentView = contentView; 4484 return this; 4485 } 4486 4487 /** 4488 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 4489 * 4490 * This will override the expanded layout that would otherwise be constructed by this 4491 * Builder object. 4492 */ 4493 @NonNull setCustomBigContentView(RemoteViews contentView)4494 public Builder setCustomBigContentView(RemoteViews contentView) { 4495 mN.bigContentView = contentView; 4496 return this; 4497 } 4498 4499 /** 4500 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 4501 * 4502 * This will override the heads-up layout that would otherwise be constructed by this 4503 * Builder object. 4504 */ 4505 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)4506 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 4507 mN.headsUpContentView = contentView; 4508 return this; 4509 } 4510 4511 /** 4512 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 4513 * 4514 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4515 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4516 * while processing broadcast receivers or services in response to notification clicks. To 4517 * launch an activity in those cases, provide a {@link PendingIntent} for the activity 4518 * itself. 4519 * 4520 * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 4521 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 4522 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 4523 * to assign PendingIntents to individual views in that custom layout (i.e., to create 4524 * clickable buttons inside the notification view). 4525 * 4526 * @see Notification#contentIntent Notification.contentIntent 4527 */ 4528 @NonNull setContentIntent(PendingIntent intent)4529 public Builder setContentIntent(PendingIntent intent) { 4530 mN.contentIntent = intent; 4531 return this; 4532 } 4533 4534 /** 4535 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 4536 * 4537 * @see Notification#deleteIntent 4538 */ 4539 @NonNull setDeleteIntent(PendingIntent intent)4540 public Builder setDeleteIntent(PendingIntent intent) { 4541 mN.deleteIntent = intent; 4542 return this; 4543 } 4544 4545 /** 4546 * An intent to launch instead of posting the notification to the status bar. 4547 * Only for use with extremely high-priority notifications demanding the user's 4548 * <strong>immediate</strong> attention, such as an incoming phone call or 4549 * alarm clock that the user has explicitly set to a particular time. 4550 * If this facility is used for something else, please give the user an option 4551 * to turn it off and use a normal notification, as this can be extremely 4552 * disruptive. 4553 * 4554 * <p> 4555 * The system UI may choose to display a heads-up notification, instead of 4556 * launching this intent, while the user is using the device. 4557 * </p> 4558 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 4559 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 4560 * use full screen intents.</p> 4561 * <p> 4562 * To be launched as a full screen intent, the notification must also be posted to a 4563 * channel with importance level set to IMPORTANCE_HIGH or higher. 4564 * </p> 4565 * 4566 * @param intent The pending intent to launch. 4567 * @param highPriority Passing true will cause this notification to be sent 4568 * even if other notifications are suppressed. 4569 * 4570 * @see Notification#fullScreenIntent 4571 */ 4572 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)4573 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 4574 mN.fullScreenIntent = intent; 4575 setFlag(FLAG_HIGH_PRIORITY, highPriority); 4576 return this; 4577 } 4578 4579 /** 4580 * Set the "ticker" text which is sent to accessibility services. 4581 * 4582 * @see Notification#tickerText 4583 */ 4584 @NonNull setTicker(CharSequence tickerText)4585 public Builder setTicker(CharSequence tickerText) { 4586 mN.tickerText = safeCharSequence(tickerText); 4587 return this; 4588 } 4589 4590 /** 4591 * Obsolete version of {@link #setTicker(CharSequence)}. 4592 * 4593 */ 4594 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)4595 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 4596 setTicker(tickerText); 4597 // views is ignored 4598 return this; 4599 } 4600 4601 /** 4602 * Add a large icon to the notification content view. 4603 * 4604 * In the platform template, this image will be shown either on the right of the 4605 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4606 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4607 */ 4608 @NonNull setLargeIcon(Bitmap b)4609 public Builder setLargeIcon(Bitmap b) { 4610 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4611 } 4612 4613 /** 4614 * Add a large icon to the notification content view. 4615 * 4616 * In the platform template, this image will be shown either on the right of the 4617 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4618 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4619 */ 4620 @NonNull setLargeIcon(Icon icon)4621 public Builder setLargeIcon(Icon icon) { 4622 mN.mLargeIcon = icon; 4623 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 4624 return this; 4625 } 4626 4627 /** 4628 * Set the sound to play. 4629 * 4630 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 4631 * for notifications. 4632 * 4633 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4634 */ 4635 @Deprecated setSound(Uri sound)4636 public Builder setSound(Uri sound) { 4637 mN.sound = sound; 4638 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 4639 return this; 4640 } 4641 4642 /** 4643 * Set the sound to play, along with a specific stream on which to play it. 4644 * 4645 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 4646 * 4647 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 4648 */ 4649 @Deprecated setSound(Uri sound, int streamType)4650 public Builder setSound(Uri sound, int streamType) { 4651 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 4652 mN.sound = sound; 4653 mN.audioStreamType = streamType; 4654 return this; 4655 } 4656 4657 /** 4658 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 4659 * use during playback. 4660 * 4661 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4662 * @see Notification#sound 4663 */ 4664 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)4665 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 4666 mN.sound = sound; 4667 mN.audioAttributes = audioAttributes; 4668 return this; 4669 } 4670 4671 /** 4672 * Set the vibration pattern to use. 4673 * 4674 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 4675 * <code>pattern</code> parameter. 4676 * 4677 * <p> 4678 * A notification that vibrates is more likely to be presented as a heads-up notification. 4679 * </p> 4680 * 4681 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 4682 * @see Notification#vibrate 4683 */ 4684 @Deprecated setVibrate(long[] pattern)4685 public Builder setVibrate(long[] pattern) { 4686 mN.vibrate = pattern; 4687 return this; 4688 } 4689 4690 /** 4691 * Set the desired color for the indicator LED on the device, as well as the 4692 * blink duty cycle (specified in milliseconds). 4693 * 4694 4695 * Not all devices will honor all (or even any) of these values. 4696 * 4697 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 4698 * @see Notification#ledARGB 4699 * @see Notification#ledOnMS 4700 * @see Notification#ledOffMS 4701 */ 4702 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)4703 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 4704 mN.ledARGB = argb; 4705 mN.ledOnMS = onMs; 4706 mN.ledOffMS = offMs; 4707 if (onMs != 0 || offMs != 0) { 4708 mN.flags |= FLAG_SHOW_LIGHTS; 4709 } 4710 return this; 4711 } 4712 4713 /** 4714 * Set whether this is an "ongoing" notification. 4715 * 4716 * Ongoing notifications cannot be dismissed by the user on locked devices, or by 4717 * notification listeners, and some notifications (call, device management, media) cannot 4718 * be dismissed on unlocked devices, so your application or service must take care of 4719 * canceling them. 4720 * 4721 * They are typically used to indicate a background task that the user is actively engaged 4722 * with (e.g., playing music) or is pending in some way and therefore occupying the device 4723 * (e.g., a file download, sync operation, active network connection). 4724 * 4725 * @see Notification#FLAG_ONGOING_EVENT 4726 */ 4727 @NonNull setOngoing(boolean ongoing)4728 public Builder setOngoing(boolean ongoing) { 4729 setFlag(FLAG_ONGOING_EVENT, ongoing); 4730 return this; 4731 } 4732 4733 /** 4734 * Set whether this notification should be colorized. When set, the color set with 4735 * {@link #setColor(int)} will be used as the background color of this notification. 4736 * <p> 4737 * This should only be used for high priority ongoing tasks like navigation, an ongoing 4738 * call, or other similarly high-priority events for the user. 4739 * <p> 4740 * For most styles, the coloring will only be applied if the notification is for a 4741 * foreground service notification. 4742 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 4743 * that have a media session attached there is no such requirement. 4744 * 4745 * @see #setColor(int) 4746 * @see MediaStyle#setMediaSession(MediaSession.Token) 4747 */ 4748 @NonNull setColorized(boolean colorize)4749 public Builder setColorized(boolean colorize) { 4750 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 4751 return this; 4752 } 4753 4754 /** 4755 * Set this flag if you would only like the sound, vibrate 4756 * and ticker to be played if the notification is not already showing. 4757 * 4758 * Note that using this flag will stop any ongoing alerting behaviour such 4759 * as sound, vibration or blinking notification LED. 4760 * 4761 * @see Notification#FLAG_ONLY_ALERT_ONCE 4762 */ 4763 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)4764 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 4765 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 4766 return this; 4767 } 4768 4769 /** 4770 * Specify a desired visibility policy for a Notification associated with a 4771 * foreground service. By default, the system can choose to defer 4772 * visibility of the notification for a short time after the service is 4773 * started. Pass 4774 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE} 4775 * to this method in order to guarantee that visibility is never deferred. Pass 4776 * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4777 * to request that visibility is deferred whenever possible. 4778 * 4779 * <p class="note">Note that deferred visibility is not guaranteed. There 4780 * may be some circumstances under which the system will show the foreground 4781 * service's associated Notification immediately even when the app has used 4782 * this method to explicitly request deferred display.</p> 4783 * @param behavior One of 4784 * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT}, 4785 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}, 4786 * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4787 * @return 4788 */ 4789 @NonNull setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4790 public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) { 4791 mN.mFgsDeferBehavior = behavior; 4792 return this; 4793 } 4794 4795 /** 4796 * Make this notification automatically dismissed when the user touches it. 4797 * 4798 * @see Notification#FLAG_AUTO_CANCEL 4799 */ 4800 @NonNull setAutoCancel(boolean autoCancel)4801 public Builder setAutoCancel(boolean autoCancel) { 4802 setFlag(FLAG_AUTO_CANCEL, autoCancel); 4803 return this; 4804 } 4805 4806 /** 4807 * Set whether or not this notification should not bridge to other devices. 4808 * 4809 * <p>Some notifications can be bridged to other devices for remote display. 4810 * This hint can be set to recommend this notification not be bridged. 4811 */ 4812 @NonNull setLocalOnly(boolean localOnly)4813 public Builder setLocalOnly(boolean localOnly) { 4814 setFlag(FLAG_LOCAL_ONLY, localOnly); 4815 return this; 4816 } 4817 4818 /** 4819 * Set which notification properties will be inherited from system defaults. 4820 * <p> 4821 * The value should be one or more of the following fields combined with 4822 * bitwise-or: 4823 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 4824 * <p> 4825 * For all default values, use {@link #DEFAULT_ALL}. 4826 * 4827 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 4828 * {@link NotificationChannel#enableLights(boolean)} and 4829 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4830 */ 4831 @Deprecated setDefaults(int defaults)4832 public Builder setDefaults(int defaults) { 4833 mN.defaults = defaults; 4834 return this; 4835 } 4836 4837 /** 4838 * Set the priority of this notification. 4839 * 4840 * @see Notification#priority 4841 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 4842 */ 4843 @Deprecated setPriority(@riority int pri)4844 public Builder setPriority(@Priority int pri) { 4845 mN.priority = pri; 4846 return this; 4847 } 4848 4849 /** 4850 * Set the notification category. 4851 * 4852 * @see Notification#category 4853 */ 4854 @NonNull setCategory(String category)4855 public Builder setCategory(String category) { 4856 mN.category = category; 4857 return this; 4858 } 4859 4860 /** 4861 * Add a person that is relevant to this notification. 4862 * 4863 * <P> 4864 * Depending on user preferences, this annotation may allow the notification to pass 4865 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4866 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4867 * appear more prominently in the user interface. 4868 * </P> 4869 * 4870 * <P> 4871 * The person should be specified by the {@code String} representation of a 4872 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 4873 * </P> 4874 * 4875 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 4876 * URIs. The path part of these URIs must exist in the contacts database, in the 4877 * appropriate column, or the reference will be discarded as invalid. Telephone schema 4878 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 4879 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 4880 * identify a person without an entry in the contacts database. 4881 * </P> 4882 * 4883 * @param uri A URI for the person. 4884 * @see Notification#EXTRA_PEOPLE 4885 * @deprecated use {@link #addPerson(Person)} 4886 */ addPerson(String uri)4887 public Builder addPerson(String uri) { 4888 addPerson(new Person.Builder().setUri(uri).build()); 4889 return this; 4890 } 4891 4892 /** 4893 * Add a person that is relevant to this notification. 4894 * 4895 * <P> 4896 * Depending on user preferences, this annotation may allow the notification to pass 4897 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4898 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4899 * appear more prominently in the user interface. 4900 * </P> 4901 * 4902 * <P> 4903 * A person should usually contain a uri in order to benefit from the ranking boost. 4904 * However, even if no uri is provided, it's beneficial to provide other people in the 4905 * notification, such that listeners and voice only devices can announce and handle them 4906 * properly. 4907 * </P> 4908 * 4909 * @param person the person to add. 4910 * @see Notification#EXTRA_PEOPLE_LIST 4911 */ 4912 @NonNull addPerson(Person person)4913 public Builder addPerson(Person person) { 4914 mPersonList.add(person); 4915 return this; 4916 } 4917 4918 /** 4919 * Set this notification to be part of a group of notifications sharing the same key. 4920 * Grouped notifications may display in a cluster or stack on devices which 4921 * support such rendering. 4922 * 4923 * <p>To make this notification the summary for its group, also call 4924 * {@link #setGroupSummary}. A sort order can be specified for group members by using 4925 * {@link #setSortKey}. 4926 * @param groupKey The group key of the group. 4927 * @return this object for method chaining 4928 */ 4929 @NonNull setGroup(String groupKey)4930 public Builder setGroup(String groupKey) { 4931 mN.mGroupKey = groupKey; 4932 return this; 4933 } 4934 4935 /** 4936 * Set this notification to be the group summary for a group of notifications. 4937 * Grouped notifications may display in a cluster or stack on devices which 4938 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 4939 * The group summary may be suppressed if too few notifications are included in the group. 4940 * @param isGroupSummary Whether this notification should be a group summary. 4941 * @return this object for method chaining 4942 */ 4943 @NonNull setGroupSummary(boolean isGroupSummary)4944 public Builder setGroupSummary(boolean isGroupSummary) { 4945 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 4946 return this; 4947 } 4948 4949 /** 4950 * Set a sort key that orders this notification among other notifications from the 4951 * same package. This can be useful if an external sort was already applied and an app 4952 * would like to preserve this. Notifications will be sorted lexicographically using this 4953 * value, although providing different priorities in addition to providing sort key may 4954 * cause this value to be ignored. 4955 * 4956 * <p>This sort key can also be used to order members of a notification group. See 4957 * {@link #setGroup}. 4958 * 4959 * @see String#compareTo(String) 4960 */ 4961 @NonNull setSortKey(String sortKey)4962 public Builder setSortKey(String sortKey) { 4963 mN.mSortKey = sortKey; 4964 return this; 4965 } 4966 4967 /** 4968 * Merge additional metadata into this notification. 4969 * 4970 * <p>Values within the Bundle will replace existing extras values in this Builder. 4971 * 4972 * @see Notification#extras 4973 */ 4974 @NonNull addExtras(Bundle extras)4975 public Builder addExtras(Bundle extras) { 4976 if (extras != null) { 4977 mUserExtras.putAll(extras); 4978 } 4979 return this; 4980 } 4981 4982 /** 4983 * Set metadata for this notification. 4984 * 4985 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 4986 * current contents are copied into the Notification each time {@link #build()} is 4987 * called. 4988 * 4989 * <p>Replaces any existing extras values with those from the provided Bundle. 4990 * Use {@link #addExtras} to merge in metadata instead. 4991 * 4992 * @see Notification#extras 4993 */ 4994 @NonNull setExtras(Bundle extras)4995 public Builder setExtras(Bundle extras) { 4996 if (extras != null) { 4997 mUserExtras = extras; 4998 } 4999 return this; 5000 } 5001 5002 /** 5003 * Get the current metadata Bundle used by this notification Builder. 5004 * 5005 * <p>The returned Bundle is shared with this Builder. 5006 * 5007 * <p>The current contents of this Bundle are copied into the Notification each time 5008 * {@link #build()} is called. 5009 * 5010 * @see Notification#extras 5011 */ getExtras()5012 public Bundle getExtras() { 5013 return mUserExtras; 5014 } 5015 5016 /** 5017 * Add an action to this notification. Actions are typically displayed by 5018 * the system as a button adjacent to the notification content. 5019 * <p> 5020 * Every action must have an icon (32dp square and matching the 5021 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 5022 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 5023 * <p> 5024 * A notification in its expanded form can display up to 3 actions, from left to right in 5025 * the order they were added. Actions will not be displayed when the notification is 5026 * collapsed, however, so be sure that any essential functions may be accessed by the user 5027 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 5028 * <p> 5029 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 5030 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 5031 * while processing broadcast receivers or services in response to notification action 5032 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the 5033 * activity itself. 5034 * <p> 5035 * As of Android {@link android.os.Build.VERSION_CODES#N}, 5036 * action button icons will not be displayed on action buttons, but are still required 5037 * and are available to 5038 * {@link android.service.notification.NotificationListenerService notification listeners}, 5039 * which may display them in other contexts, for example on a wearable device. 5040 * 5041 * @param icon Resource ID of a drawable that represents the action. 5042 * @param title Text describing the action. 5043 * @param intent PendingIntent to be fired when the action is invoked. 5044 * 5045 * @deprecated Use {@link #addAction(Action)} instead. 5046 */ 5047 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)5048 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 5049 mActions.add(new Action(icon, safeCharSequence(title), intent)); 5050 return this; 5051 } 5052 5053 /** 5054 * Add an action to this notification. Actions are typically displayed by 5055 * the system as a button adjacent to the notification content. 5056 * <p> 5057 * Every action must have an icon (32dp square and matching the 5058 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 5059 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 5060 * <p> 5061 * A notification in its expanded form can display up to 3 actions, from left to right in 5062 * the order they were added. Actions will not be displayed when the notification is 5063 * collapsed, however, so be sure that any essential functions may be accessed by the user 5064 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 5065 * 5066 * @param action The action to add. 5067 */ 5068 @NonNull addAction(Action action)5069 public Builder addAction(Action action) { 5070 if (action != null) { 5071 mActions.add(action); 5072 } 5073 return this; 5074 } 5075 5076 /** 5077 * Alter the complete list of actions attached to this notification. 5078 * @see #addAction(Action). 5079 * 5080 * @param actions 5081 * @return 5082 */ 5083 @NonNull setActions(Action... actions)5084 public Builder setActions(Action... actions) { 5085 mActions.clear(); 5086 for (int i = 0; i < actions.length; i++) { 5087 if (actions[i] != null) { 5088 mActions.add(actions[i]); 5089 } 5090 } 5091 return this; 5092 } 5093 5094 /** 5095 * Add a rich notification style to be applied at build time. 5096 * 5097 * @param style Object responsible for modifying the notification style. 5098 */ 5099 @NonNull setStyle(Style style)5100 public Builder setStyle(Style style) { 5101 if (mStyle != style) { 5102 mStyle = style; 5103 if (mStyle != null) { 5104 mStyle.setBuilder(this); 5105 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 5106 } else { 5107 mN.extras.remove(EXTRA_TEMPLATE); 5108 } 5109 } 5110 return this; 5111 } 5112 5113 /** 5114 * Returns the style set by {@link #setStyle(Style)}. 5115 */ getStyle()5116 public Style getStyle() { 5117 return mStyle; 5118 } 5119 5120 /** 5121 * Specify the value of {@link #visibility}. 5122 * 5123 * @return The same Builder. 5124 */ 5125 @NonNull setVisibility(@isibility int visibility)5126 public Builder setVisibility(@Visibility int visibility) { 5127 mN.visibility = visibility; 5128 return this; 5129 } 5130 5131 /** 5132 * Supply a replacement Notification whose contents should be shown in insecure contexts 5133 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 5134 * @param n A replacement notification, presumably with some or all info redacted. 5135 * @return The same Builder. 5136 */ 5137 @NonNull setPublicVersion(Notification n)5138 public Builder setPublicVersion(Notification n) { 5139 if (n != null) { 5140 mN.publicVersion = new Notification(); 5141 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 5142 } else { 5143 mN.publicVersion = null; 5144 } 5145 return this; 5146 } 5147 5148 /** 5149 * Apply an extender to this notification builder. Extenders may be used to add 5150 * metadata or change options on this builder. 5151 */ 5152 @NonNull extend(Extender extender)5153 public Builder extend(Extender extender) { 5154 extender.extend(this); 5155 return this; 5156 } 5157 5158 /** 5159 * Set the value for a notification flag 5160 * 5161 * @param mask Bit mask of the flag 5162 * @param value Status (on/off) of the flag 5163 * 5164 * @return The same Builder. 5165 */ 5166 @NonNull setFlag(@otificationFlags int mask, boolean value)5167 public Builder setFlag(@NotificationFlags int mask, boolean value) { 5168 if (value) { 5169 mN.flags |= mask; 5170 } else { 5171 mN.flags &= ~mask; 5172 } 5173 return this; 5174 } 5175 5176 /** 5177 * Sets {@link Notification#color}. 5178 * 5179 * @param argb The accent color to use 5180 * 5181 * @return The same Builder. 5182 */ 5183 @NonNull setColor(@olorInt int argb)5184 public Builder setColor(@ColorInt int argb) { 5185 mN.color = argb; 5186 sanitizeColor(); 5187 return this; 5188 } 5189 bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5190 private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { 5191 contentView.setDrawableTint( 5192 R.id.phishing_alert, 5193 false /* targetBackground */, 5194 getColors(p).getErrorColor(), 5195 PorterDuff.Mode.SRC_ATOP); 5196 } 5197 getProfileBadgeDrawable()5198 private Drawable getProfileBadgeDrawable() { 5199 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 5200 // This user can never be a badged profile, 5201 // and also includes USER_ALL system notifications. 5202 return null; 5203 } 5204 // Note: This assumes that the current user can read the profile badge of the 5205 // originating user. 5206 DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); 5207 return dpm.getResources().getDrawable( 5208 getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION, 5209 this::getDefaultProfileBadgeDrawable); 5210 } 5211 getUpdatableProfileBadgeId()5212 private String getUpdatableProfileBadgeId() { 5213 return mContext.getSystemService(UserManager.class).isManagedProfile() 5214 ? WORK_PROFILE_ICON : UNDEFINED; 5215 } 5216 getDefaultProfileBadgeDrawable()5217 private Drawable getDefaultProfileBadgeDrawable() { 5218 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 5219 new UserHandle(mContext.getUserId()), 0); 5220 } 5221 getProfileBadge()5222 private Bitmap getProfileBadge() { 5223 Drawable badge = getProfileBadgeDrawable(); 5224 if (badge == null) { 5225 return null; 5226 } 5227 final int size = mContext.getResources().getDimensionPixelSize( 5228 R.dimen.notification_badge_size); 5229 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 5230 Canvas canvas = new Canvas(bitmap); 5231 badge.setBounds(0, 0, size, size); 5232 badge.draw(canvas); 5233 return bitmap; 5234 } 5235 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5236 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 5237 Bitmap profileBadge = getProfileBadge(); 5238 5239 if (profileBadge != null) { 5240 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 5241 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 5242 if (isBackgroundColorized(p)) { 5243 contentView.setDrawableTint(R.id.profile_badge, false, 5244 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 5245 } 5246 } 5247 } 5248 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5249 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 5250 contentView.setDrawableTint( 5251 R.id.alerted_icon, 5252 false /* targetBackground */, 5253 getColors(p).getSecondaryTextColor(), 5254 PorterDuff.Mode.SRC_IN); 5255 } 5256 5257 /** 5258 * @hide 5259 */ usesStandardHeader()5260 public boolean usesStandardHeader() { 5261 if (mN.mUsesStandardHeader) { 5262 return true; 5263 } 5264 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 5265 if (mN.contentView == null && mN.bigContentView == null) { 5266 return true; 5267 } 5268 } 5269 boolean contentViewUsesHeader = mN.contentView == null 5270 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 5271 boolean bigContentViewUsesHeader = mN.bigContentView == null 5272 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 5273 return contentViewUsesHeader && bigContentViewUsesHeader; 5274 } 5275 resetStandardTemplate(RemoteViews contentView)5276 private void resetStandardTemplate(RemoteViews contentView) { 5277 resetNotificationHeader(contentView); 5278 contentView.setViewVisibility(R.id.right_icon, View.GONE); 5279 contentView.setViewVisibility(R.id.title, View.GONE); 5280 contentView.setTextViewText(R.id.title, null); 5281 contentView.setViewVisibility(R.id.text, View.GONE); 5282 contentView.setTextViewText(R.id.text, null); 5283 } 5284 5285 /** 5286 * Resets the notification header to its original state 5287 */ resetNotificationHeader(RemoteViews contentView)5288 private void resetNotificationHeader(RemoteViews contentView) { 5289 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 5290 // re-using the drawable when the notification is updated. 5291 contentView.setBoolean(R.id.expand_button, "setExpanded", false); 5292 contentView.setViewVisibility(R.id.app_name_text, View.GONE); 5293 contentView.setTextViewText(R.id.app_name_text, null); 5294 contentView.setViewVisibility(R.id.chronometer, View.GONE); 5295 contentView.setViewVisibility(R.id.header_text, View.GONE); 5296 contentView.setTextViewText(R.id.header_text, null); 5297 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 5298 contentView.setTextViewText(R.id.header_text_secondary, null); 5299 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 5300 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 5301 contentView.setViewVisibility(R.id.time_divider, View.GONE); 5302 contentView.setViewVisibility(R.id.time, View.GONE); 5303 contentView.setImageViewIcon(R.id.profile_badge, null); 5304 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 5305 mN.mUsesStandardHeader = false; 5306 } 5307 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5308 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 5309 TemplateBindResult result) { 5310 p.headerless(resId == getBaseLayoutResource() 5311 || resId == getHeadsUpBaseLayoutResource() 5312 || resId == getMessagingLayoutResource() 5313 || resId == R.layout.notification_template_material_media); 5314 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 5315 5316 resetStandardTemplate(contentView); 5317 5318 final Bundle ex = mN.extras; 5319 updateBackgroundColor(contentView, p); 5320 bindNotificationHeader(contentView, p); 5321 bindLargeIconAndApplyMargin(contentView, p, result); 5322 boolean showProgress = handleProgressBar(contentView, ex, p); 5323 boolean hasSecondLine = showProgress; 5324 if (p.hasTitle()) { 5325 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE); 5326 contentView.setTextViewText(p.mTitleViewId, ensureColorSpanContrast(p.mTitle, p)); 5327 setTextViewColorPrimary(contentView, p.mTitleViewId, p); 5328 } else if (p.mTitleViewId != R.id.title) { 5329 // This alternate title view ID is not cleared by resetStandardTemplate 5330 contentView.setViewVisibility(p.mTitleViewId, View.GONE); 5331 contentView.setTextViewText(p.mTitleViewId, null); 5332 } 5333 if (p.mText != null && p.mText.length() != 0 5334 && (!showProgress || p.mAllowTextWithProgress)) { 5335 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE); 5336 contentView.setTextViewText(p.mTextViewId, ensureColorSpanContrast(p.mText, p)); 5337 setTextViewColorSecondary(contentView, p.mTextViewId, p); 5338 hasSecondLine = true; 5339 } else if (p.mTextViewId != R.id.text) { 5340 // This alternate text view ID is not cleared by resetStandardTemplate 5341 contentView.setViewVisibility(p.mTextViewId, View.GONE); 5342 contentView.setTextViewText(p.mTextViewId, null); 5343 } 5344 setHeaderlessVerticalMargins(contentView, p, hasSecondLine); 5345 5346 return contentView; 5347 } 5348 setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5349 private static void setHeaderlessVerticalMargins(RemoteViews contentView, 5350 StandardTemplateParams p, boolean hasSecondLine) { 5351 if (!p.mHeaderless) { 5352 return; 5353 } 5354 int marginDimen = hasSecondLine 5355 ? R.dimen.notification_headerless_margin_twoline 5356 : R.dimen.notification_headerless_margin_oneline; 5357 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5358 RemoteViews.MARGIN_TOP, marginDimen); 5359 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5360 RemoteViews.MARGIN_BOTTOM, marginDimen); 5361 } 5362 setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5363 private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, 5364 StandardTemplateParams p) { 5365 contentView.setTextColor(id, getPrimaryTextColor(p)); 5366 } 5367 5368 /** 5369 * @param p the template params to inflate this with 5370 * @return the primary text color 5371 * @hide 5372 */ 5373 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)5374 public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { 5375 return getColors(p).getPrimaryTextColor(); 5376 } 5377 5378 /** 5379 * @param p the template params to inflate this with 5380 * @return the secondary text color 5381 * @hide 5382 */ 5383 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)5384 public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { 5385 return getColors(p).getSecondaryTextColor(); 5386 } 5387 setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5388 private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, 5389 StandardTemplateParams p) { 5390 contentView.setTextColor(id, getSecondaryTextColor(p)); 5391 } 5392 getColors(StandardTemplateParams p)5393 private Colors getColors(StandardTemplateParams p) { 5394 mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode); 5395 return mColors; 5396 } 5397 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5398 private void updateBackgroundColor(RemoteViews contentView, 5399 StandardTemplateParams p) { 5400 if (isBackgroundColorized(p)) { 5401 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 5402 getBackgroundColor(p)); 5403 } else { 5404 // Clear it! 5405 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 5406 0); 5407 } 5408 } 5409 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5410 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 5411 StandardTemplateParams p) { 5412 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 5413 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 5414 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5415 if (!p.mHideProgress && (max != 0 || ind)) { 5416 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 5417 contentView.setProgressBar(R.id.progress, max, progress, ind); 5418 contentView.setProgressBackgroundTintList(R.id.progress, 5419 mContext.getColorStateList(R.color.notification_progress_background_color)); 5420 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p)); 5421 contentView.setProgressTintList(R.id.progress, progressTint); 5422 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint); 5423 return true; 5424 } else { 5425 contentView.setViewVisibility(R.id.progress, View.GONE); 5426 return false; 5427 } 5428 } 5429 bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5430 private void bindLargeIconAndApplyMargin(RemoteViews contentView, 5431 @NonNull StandardTemplateParams p, 5432 @Nullable TemplateBindResult result) { 5433 if (result == null) { 5434 result = new TemplateBindResult(); 5435 } 5436 bindLargeIcon(contentView, p, result); 5437 if (!p.mHeaderless) { 5438 // views in states with a header (big states) 5439 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header); 5440 result.mTitleMarginSet.applyToView(contentView, R.id.title); 5441 // If there is no title, the text (or big_text) needs to wrap around the image 5442 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId); 5443 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1); 5444 } 5445 } 5446 5447 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5448 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5449 // the change's state in NotificationManagerService were very complex. These behavior 5450 // changes are entirely visual, and should otherwise be undetectable by apps. 5451 @SuppressWarnings("AndroidFrameworkCompatChange") calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5452 private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, 5453 @NonNull TemplateBindResult result) { 5454 final Resources resources = mContext.getResources(); 5455 final float density = resources.getDisplayMetrics().density; 5456 final float iconMarginDp = resources.getDimension( 5457 R.dimen.notification_right_icon_content_margin) / density; 5458 final float contentMarginDp = resources.getDimension( 5459 R.dimen.notification_content_margin_end) / density; 5460 final float expanderSizeDp = resources.getDimension( 5461 R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; 5462 final float viewHeightDp = resources.getDimension( 5463 R.dimen.notification_right_icon_size) / density; 5464 float viewWidthDp = viewHeightDp; // icons are 1:1 by default 5465 if (rightIcon != null && (isPromotedPicture 5466 || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) { 5467 Drawable drawable = rightIcon.loadDrawable(mContext); 5468 if (drawable != null) { 5469 int iconWidth = drawable.getIntrinsicWidth(); 5470 int iconHeight = drawable.getIntrinsicHeight(); 5471 if (iconWidth > iconHeight && iconHeight > 0) { 5472 final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO; 5473 viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight, 5474 maxViewWidthDp); 5475 } 5476 } 5477 } 5478 final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; 5479 result.setRightIconState(rightIcon != null /* visible */, viewWidthDp, 5480 viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp); 5481 } 5482 5483 /** 5484 * Bind the large icon. 5485 */ bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5486 private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, 5487 @NonNull TemplateBindResult result) { 5488 if (mN.mLargeIcon == null && mN.largeIcon != null) { 5489 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 5490 } 5491 5492 // Determine the left and right icons 5493 Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon; 5494 Icon rightIcon = p.mHideRightIcon ? null 5495 : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon); 5496 5497 // Apply the left icon (without duplicating the bitmap) 5498 if (leftIcon != rightIcon || leftIcon == null) { 5499 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it 5500 // explicitly and make sure it won't take the right_icon drawable. 5501 contentView.setImageViewIcon(R.id.left_icon, leftIcon); 5502 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0); 5503 } else { 5504 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon 5505 // drawable. This avoids the view having two copies of the same bitmap. 5506 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1); 5507 } 5508 5509 // Always calculate dimens to populate `result` for the GONE case 5510 boolean isPromotedPicture = p.mPromotedPicture != null; 5511 calculateRightIconDimens(rightIcon, isPromotedPicture, result); 5512 5513 // Bind the right icon 5514 if (rightIcon != null) { 5515 contentView.setViewLayoutWidth(R.id.right_icon, 5516 result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP); 5517 contentView.setViewLayoutHeight(R.id.right_icon, 5518 result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP); 5519 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 5520 contentView.setImageViewIcon(R.id.right_icon, rightIcon); 5521 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 5522 isPromotedPicture ? 1 : 0); 5523 processLargeLegacyIcon(rightIcon, contentView, p); 5524 } else { 5525 // The "reset" doesn't clear the drawable, so we do it here. This clear is 5526 // important because the presence of a drawable in this view (regardless of the 5527 // visibility) is used by NotificationGroupingUtil to set the visibility. 5528 contentView.setImageViewIcon(R.id.right_icon, null); 5529 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0); 5530 } 5531 } 5532 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5533 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 5534 bindSmallIcon(contentView, p); 5535 // Populate text left-to-right so that separators are only shown between strings 5536 boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */); 5537 hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft); 5538 hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft); 5539 if (!hasTextToLeft) { 5540 // If there's still no text, force add the app name so there is some text. 5541 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */); 5542 } 5543 bindHeaderChronometerAndTime(contentView, p, hasTextToLeft); 5544 bindPhishingAlertIcon(contentView, p); 5545 bindProfileBadge(contentView, p); 5546 bindAlertedIcon(contentView, p); 5547 bindExpandButton(contentView, p); 5548 mN.mUsesStandardHeader = true; 5549 } 5550 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5551 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 5552 // set default colors 5553 int bgColor = getBackgroundColor(p); 5554 int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor); 5555 int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor); 5556 contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor); 5557 contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); 5558 // Use different highlighted colors for conversations' unread count 5559 if (p.mHighlightExpander) { 5560 pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor); 5561 textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor); 5562 } 5563 contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); 5564 contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); 5565 } 5566 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5567 private void bindHeaderChronometerAndTime(RemoteViews contentView, 5568 StandardTemplateParams p, boolean hasTextToLeft) { 5569 if (!p.mHideTime && showsTimeOrChronometer()) { 5570 if (hasTextToLeft) { 5571 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 5572 setTextViewColorSecondary(contentView, R.id.time_divider, p); 5573 } 5574 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 5575 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 5576 contentView.setLong(R.id.chronometer, "setBase", 5577 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 5578 contentView.setBoolean(R.id.chronometer, "setStarted", true); 5579 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 5580 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 5581 setTextViewColorSecondary(contentView, R.id.chronometer, p); 5582 } else { 5583 contentView.setViewVisibility(R.id.time, View.VISIBLE); 5584 contentView.setLong(R.id.time, "setTime", mN.when); 5585 setTextViewColorSecondary(contentView, R.id.time, p); 5586 } 5587 } else { 5588 // We still want a time to be set but gone, such that we can show and hide it 5589 // on demand in case it's a child notification without anything in the header 5590 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 5591 setTextViewColorSecondary(contentView, R.id.time, p); 5592 } 5593 } 5594 5595 /** 5596 * @return true if the header text will be visible 5597 */ bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5598 private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p, 5599 boolean hasTextToLeft) { 5600 if (p.mHideSubText) { 5601 return false; 5602 } 5603 CharSequence headerText = p.mSubText; 5604 if (headerText == null && mStyle != null && mStyle.mSummaryTextSet 5605 && mStyle.hasSummaryInHeader()) { 5606 headerText = mStyle.mSummaryText; 5607 } 5608 if (headerText == null 5609 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 5610 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 5611 headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 5612 } 5613 if (!TextUtils.isEmpty(headerText)) { 5614 contentView.setTextViewText(R.id.header_text, ensureColorSpanContrast( 5615 processLegacyText(headerText), p)); 5616 setTextViewColorSecondary(contentView, R.id.header_text, p); 5617 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 5618 if (hasTextToLeft) { 5619 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 5620 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 5621 } 5622 return true; 5623 } 5624 return false; 5625 } 5626 5627 /** 5628 * @return true if the secondary header text will be visible 5629 */ bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5630 private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, 5631 boolean hasTextToLeft) { 5632 if (p.mHideSubText) { 5633 return false; 5634 } 5635 if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) { 5636 contentView.setTextViewText(R.id.header_text_secondary, ensureColorSpanContrast( 5637 processLegacyText(p.mHeaderTextSecondary), p)); 5638 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 5639 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 5640 if (hasTextToLeft) { 5641 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 5642 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 5643 } 5644 return true; 5645 } 5646 return false; 5647 } 5648 5649 /** 5650 * @hide 5651 */ 5652 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) loadHeaderAppName()5653 public String loadHeaderAppName() { 5654 CharSequence name = null; 5655 final PackageManager pm = mContext.getPackageManager(); 5656 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 5657 // only system packages which lump together a bunch of unrelated stuff 5658 // may substitute a different name to make the purpose of the 5659 // notification more clear. the correct package label should always 5660 // be accessible via SystemUI. 5661 final String pkg = mContext.getPackageName(); 5662 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 5663 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 5664 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 5665 name = subName; 5666 } else { 5667 Log.w(TAG, "warning: pkg " 5668 + pkg + " attempting to substitute app name '" + subName 5669 + "' without holding perm " 5670 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 5671 } 5672 } 5673 if (TextUtils.isEmpty(name)) { 5674 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 5675 } 5676 if (TextUtils.isEmpty(name)) { 5677 // still nothing? 5678 return null; 5679 } 5680 5681 return String.valueOf(name); 5682 } 5683 5684 /** 5685 * @return true if the app name will be visible 5686 */ bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5687 private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, 5688 boolean force) { 5689 if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) { 5690 // unless the force flag is set, don't show the app name in the minimized state. 5691 return false; 5692 } 5693 if (p.mHeaderless && p.hasTitle()) { 5694 // the headerless template will have the TITLE in this position; return true to 5695 // keep the divider visible between that title and the next text element. 5696 return true; 5697 } 5698 if (p.mHideAppName) { 5699 // The app name is being hidden, so we definitely want to return here. 5700 // Assume that there is a title which will replace it in the header. 5701 return p.hasTitle(); 5702 } 5703 contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); 5704 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 5705 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 5706 return true; 5707 } 5708 5709 /** 5710 * Determines if the notification should be colorized *for the purposes of applying colors*. 5711 * If this is the minimized view of a colorized notification, this will return false so that 5712 * internal coloring logic can still render the notification normally. 5713 */ isBackgroundColorized(StandardTemplateParams p)5714 private boolean isBackgroundColorized(StandardTemplateParams p) { 5715 return p.allowColorization && mN.isColorized(); 5716 } 5717 isCallActionColorCustomizable()5718 private boolean isCallActionColorCustomizable() { 5719 // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because 5720 // that is only used for disallowing colorization of headers for the minimized state, 5721 // and neither of those conditions applies when showing actions. 5722 // Not requiring StandardTemplateParams as an argument simplifies the creation process. 5723 return mN.isColorized() && mContext.getResources().getBoolean( 5724 R.bool.config_callNotificationActionColorsRequireColorized); 5725 } 5726 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5727 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 5728 if (mN.mSmallIcon == null && mN.icon != 0) { 5729 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 5730 } 5731 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 5732 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 5733 processSmallIconColor(mN.mSmallIcon, contentView, p); 5734 } 5735 5736 /** 5737 * @return true if the built notification will show the time or the chronometer; false 5738 * otherwise 5739 */ showsTimeOrChronometer()5740 private boolean showsTimeOrChronometer() { 5741 return mN.showsTime() || mN.showsChronometer(); 5742 } 5743 resetStandardTemplateWithActions(RemoteViews big)5744 private void resetStandardTemplateWithActions(RemoteViews big) { 5745 // actions_container is only reset when there are no actions to avoid focus issues with 5746 // remote inputs. 5747 big.setViewVisibility(R.id.actions, View.GONE); 5748 big.removeAllViews(R.id.actions); 5749 5750 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 5751 big.setTextViewText(R.id.notification_material_reply_text_1, null); 5752 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 5753 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 5754 5755 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 5756 big.setTextViewText(R.id.notification_material_reply_text_2, null); 5757 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 5758 big.setTextViewText(R.id.notification_material_reply_text_3, null); 5759 5760 // This may get erased by bindSnoozeAction 5761 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5762 RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin); 5763 } 5764 bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5765 private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) { 5766 boolean hideSnoozeButton = mN.isFgsOrUij() 5767 || mN.fullScreenIntent != null 5768 || isBackgroundColorized(p) 5769 || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG; 5770 big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton); 5771 if (hideSnoozeButton) { 5772 // Only hide; NotificationContentView will show it when it adds the click listener 5773 big.setViewVisibility(R.id.snooze_button, View.GONE); 5774 } 5775 5776 final boolean snoozeEnabled = !hideSnoozeButton 5777 && mContext.getContentResolver() != null 5778 && isSnoozeSettingEnabled(); 5779 if (snoozeEnabled) { 5780 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5781 RemoteViews.MARGIN_BOTTOM, 0); 5782 } 5783 } 5784 isSnoozeSettingEnabled()5785 private boolean isSnoozeSettingEnabled() { 5786 try { 5787 return Settings.Secure.getIntForUser(mContext.getContentResolver(), 5788 Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) == 1; 5789 } catch (SecurityException ex) { 5790 // Most 3p apps can't access this snooze setting, so their NotificationListeners 5791 // would be unable to create notification views if we propagated this exception. 5792 return false; 5793 } 5794 } 5795 5796 /** 5797 * Returns the actions that are not contextual. 5798 */ getNonContextualActions()5799 private @NonNull List<Notification.Action> getNonContextualActions() { 5800 if (mActions == null) return Collections.emptyList(); 5801 List<Notification.Action> standardActions = new ArrayList<>(); 5802 for (Notification.Action action : mActions) { 5803 if (!action.isContextual()) { 5804 standardActions.add(action); 5805 } 5806 } 5807 return standardActions; 5808 } 5809 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5810 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5811 StandardTemplateParams p, TemplateBindResult result) { 5812 RemoteViews big = applyStandardTemplate(layoutId, p, result); 5813 5814 resetStandardTemplateWithActions(big); 5815 bindSnoozeAction(big, p); 5816 // color the snooze and bubble actions with the theme color 5817 ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); 5818 big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); 5819 big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); 5820 5821 boolean validRemoteInput = false; 5822 5823 // In the UI, contextual actions appear separately from the standard actions, so we 5824 // filter them out here. 5825 List<Notification.Action> nonContextualActions = getNonContextualActions(); 5826 5827 int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS); 5828 boolean emphasizedMode = mN.fullScreenIntent != null 5829 || p.mCallStyleActions 5830 || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0); 5831 5832 if (p.mCallStyleActions) { 5833 // Clear view padding to allow buttons to start on the left edge. 5834 // This must be done before 'setEmphasizedMode' which sets top/bottom margins. 5835 big.setViewPadding(R.id.actions, 0, 0, 0, 0); 5836 // Add an optional indent that will make buttons start at the correct column when 5837 // there is enough space to do so (and fall back to the left edge if not). 5838 big.setInt(R.id.actions, "setCollapsibleIndentDimen", 5839 R.dimen.call_notification_collapsible_indent); 5840 } 5841 big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode); 5842 if (numActions > 0 && !p.mHideActions) { 5843 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 5844 big.setViewVisibility(R.id.actions, View.VISIBLE); 5845 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5846 RemoteViews.MARGIN_BOTTOM, 0); 5847 for (int i = 0; i < numActions; i++) { 5848 Action action = nonContextualActions.get(i); 5849 5850 boolean actionHasValidInput = hasValidRemoteInput(action); 5851 validRemoteInput |= actionHasValidInput; 5852 5853 final RemoteViews button = generateActionButton(action, emphasizedMode, p); 5854 if (actionHasValidInput && !emphasizedMode) { 5855 // Clear the drawable 5856 button.setInt(R.id.action0, "setBackgroundResource", 0); 5857 } 5858 if (emphasizedMode && i > 0) { 5859 // Clear start margin from non-first buttons to reduce the gap between them. 5860 // (8dp remaining gap is from all buttons' standard 4dp inset). 5861 button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0); 5862 } 5863 big.addView(R.id.actions, button); 5864 } 5865 } else { 5866 big.setViewVisibility(R.id.actions_container, View.GONE); 5867 } 5868 5869 RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( 5870 mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); 5871 if (validRemoteInput && replyText != null && replyText.length > 0 5872 && !TextUtils.isEmpty(replyText[0].getText()) 5873 && p.maxRemoteInputHistory > 0) { 5874 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 5875 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 5876 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 5877 View.VISIBLE); 5878 big.setTextViewText(R.id.notification_material_reply_text_1, 5879 ensureColorSpanContrast(replyText[0].getText(), p)); 5880 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 5881 big.setViewVisibility(R.id.notification_material_reply_progress, 5882 showSpinner ? View.VISIBLE : View.GONE); 5883 big.setProgressIndeterminateTintList( 5884 R.id.notification_material_reply_progress, 5885 ColorStateList.valueOf(getPrimaryAccentColor(p))); 5886 5887 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) 5888 && p.maxRemoteInputHistory > 1) { 5889 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 5890 big.setTextViewText(R.id.notification_material_reply_text_2, 5891 ensureColorSpanContrast(replyText[1].getText(), p)); 5892 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 5893 5894 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) 5895 && p.maxRemoteInputHistory > 2) { 5896 big.setViewVisibility( 5897 R.id.notification_material_reply_text_3, View.VISIBLE); 5898 big.setTextViewText(R.id.notification_material_reply_text_3, 5899 ensureColorSpanContrast(replyText[2].getText(), p)); 5900 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 5901 } 5902 } 5903 } 5904 5905 return big; 5906 } 5907 hasValidRemoteInput(Action action)5908 private boolean hasValidRemoteInput(Action action) { 5909 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 5910 // Weird actions 5911 return false; 5912 } 5913 5914 RemoteInput[] remoteInputs = action.getRemoteInputs(); 5915 if (remoteInputs == null) { 5916 return false; 5917 } 5918 5919 for (RemoteInput r : remoteInputs) { 5920 CharSequence[] choices = r.getChoices(); 5921 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 5922 return true; 5923 } 5924 } 5925 return false; 5926 } 5927 5928 /** 5929 * Construct a RemoteViews for the final 1U notification layout. In order: 5930 * 1. Custom contentView from the caller 5931 * 2. Style's proposed content view 5932 * 3. Standard template view 5933 */ createContentView()5934 public RemoteViews createContentView() { 5935 return createContentView(false /* increasedheight */ ); 5936 } 5937 5938 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5939 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5940 // the change's state in NotificationManagerService were very complex. While it's possible 5941 // apps can detect the change, it's most likely that the changes will simply result in 5942 // visual regressions. 5943 @SuppressWarnings("AndroidFrameworkCompatChange") fullyCustomViewRequiresDecoration(boolean fromStyle)5944 private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) { 5945 // Custom views which come from a platform style class are safe, and thus do not need to 5946 // be wrapped. Any subclass of those styles has the opportunity to make arbitrary 5947 // changes to the RemoteViews, and thus can't be trusted as a fully vetted view. 5948 if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { 5949 return false; 5950 } 5951 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S; 5952 } 5953 minimallyDecoratedContentView(@onNull RemoteViews customContent)5954 private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) { 5955 StandardTemplateParams p = mParams.reset() 5956 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 5957 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5958 .fillTextsFrom(this); 5959 TemplateBindResult result = new TemplateBindResult(); 5960 RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result); 5961 buildCustomContentIntoTemplate(mContext, standard, customContent, 5962 p, result); 5963 return standard; 5964 } 5965 minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5966 private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) { 5967 StandardTemplateParams p = mParams.reset() 5968 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 5969 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5970 .fillTextsFrom(this); 5971 TemplateBindResult result = new TemplateBindResult(); 5972 RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5973 p, result); 5974 buildCustomContentIntoTemplate(mContext, standard, customContent, 5975 p, result); 5976 makeHeaderExpanded(standard); 5977 return standard; 5978 } 5979 minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5980 private RemoteViews minimallyDecoratedHeadsUpContentView( 5981 @NonNull RemoteViews customContent) { 5982 StandardTemplateParams p = mParams.reset() 5983 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 5984 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5985 .fillTextsFrom(this); 5986 TemplateBindResult result = new TemplateBindResult(); 5987 RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), 5988 p, result); 5989 buildCustomContentIntoTemplate(mContext, standard, customContent, 5990 p, result); 5991 return standard; 5992 } 5993 5994 /** 5995 * Construct a RemoteViews for the smaller content view. 5996 * 5997 * @param increasedHeight true if this layout be created with an increased height. Some 5998 * styles may support showing more then just that basic 1U size 5999 * and the system may decide to render important notifications 6000 * slightly bigger even when collapsed. 6001 * 6002 * @hide 6003 */ createContentView(boolean increasedHeight)6004 public RemoteViews createContentView(boolean increasedHeight) { 6005 if (useExistingRemoteView(mN.contentView)) { 6006 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 6007 ? minimallyDecoratedContentView(mN.contentView) : mN.contentView; 6008 } else if (mStyle != null) { 6009 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 6010 if (styleView != null) { 6011 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 6012 ? minimallyDecoratedContentView(styleView) : styleView; 6013 } 6014 } 6015 StandardTemplateParams p = mParams.reset() 6016 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 6017 .fillTextsFrom(this); 6018 return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */); 6019 } 6020 useExistingRemoteView(RemoteViews customContent)6021 private boolean useExistingRemoteView(RemoteViews customContent) { 6022 if (customContent == null) { 6023 return false; 6024 } 6025 if (styleDisplaysCustomViewInline()) { 6026 // the provided custom view is intended to be wrapped by the style. 6027 return false; 6028 } 6029 if (fullyCustomViewRequiresDecoration(false) 6030 && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) { 6031 // If the app's custom views are objects returned from Builder.create*ContentView() 6032 // then the app is most likely attempting to spoof the user. Even if they are not, 6033 // the result would be broken (b/189189308) so we will ignore it. 6034 Log.w(TAG, "For apps targeting S, a custom content view that is a modified " 6035 + "version of any standard layout is disallowed."); 6036 return false; 6037 } 6038 return true; 6039 } 6040 6041 /** 6042 * Construct a RemoteViews for the final big notification layout. 6043 */ createBigContentView()6044 public RemoteViews createBigContentView() { 6045 RemoteViews result = null; 6046 if (useExistingRemoteView(mN.bigContentView)) { 6047 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 6048 ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView; 6049 } 6050 if (mStyle != null) { 6051 result = mStyle.makeBigContentView(); 6052 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) { 6053 result = minimallyDecoratedBigContentView(result); 6054 } 6055 } 6056 if (result == null) { 6057 if (bigContentViewRequired()) { 6058 StandardTemplateParams p = mParams.reset() 6059 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 6060 .allowTextWithProgress(true) 6061 .fillTextsFrom(this); 6062 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, 6063 null /* result */); 6064 } 6065 } 6066 makeHeaderExpanded(result); 6067 return result; 6068 } 6069 6070 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 6071 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 6072 // the change's state in NotificationManagerService were very complex. While it's possible 6073 // apps can detect the change, it's most likely that the changes will simply result in 6074 // visual regressions. 6075 @SuppressWarnings("AndroidFrameworkCompatChange") bigContentViewRequired()6076 private boolean bigContentViewRequired() { 6077 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { 6078 return true; 6079 } 6080 // Notifications with contentView and without a bigContentView, style, or actions would 6081 // not have an expanded state before S, so showing the standard template expanded state 6082 // usually looks wrong, so we keep it simple and don't show the expanded state. 6083 boolean exempt = mN.contentView != null && mN.bigContentView == null 6084 && mStyle == null && mActions.size() == 0; 6085 return !exempt; 6086 } 6087 6088 /** 6089 * Construct a RemoteViews for the final notification header only. This will not be 6090 * colorized. 6091 * 6092 * @hide 6093 */ makeNotificationGroupHeader()6094 public RemoteViews makeNotificationGroupHeader() { 6095 return makeNotificationHeader(mParams.reset() 6096 .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER) 6097 .fillTextsFrom(this)); 6098 } 6099 6100 /** 6101 * Construct a RemoteViews for the final notification header only. This will not be 6102 * colorized. 6103 * 6104 * @param p the template params to inflate this with 6105 */ makeNotificationHeader(StandardTemplateParams p)6106 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 6107 // Headers on their own are never colorized 6108 p.disallowColorization(); 6109 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 6110 R.layout.notification_template_header); 6111 resetNotificationHeader(header); 6112 bindNotificationHeader(header, p); 6113 return header; 6114 } 6115 6116 /** 6117 * Construct a RemoteViews for the ambient version of the notification. 6118 * 6119 * @hide 6120 */ makeAmbientNotification()6121 public RemoteViews makeAmbientNotification() { 6122 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 6123 if (headsUpContentView != null) { 6124 return headsUpContentView; 6125 } 6126 return createContentView(); 6127 } 6128 6129 /** 6130 * Adapt the Notification header if this view is used as an expanded view. 6131 * 6132 * @hide 6133 */ makeHeaderExpanded(RemoteViews result)6134 public static void makeHeaderExpanded(RemoteViews result) { 6135 if (result != null) { 6136 result.setBoolean(R.id.expand_button, "setExpanded", true); 6137 } 6138 } 6139 6140 /** 6141 * Construct a RemoteViews for the final heads-up notification layout. 6142 * 6143 * @param increasedHeight true if this layout be created with an increased height. Some 6144 * styles may support showing more then just that basic 1U size 6145 * and the system may decide to render important notifications 6146 * slightly bigger even when collapsed. 6147 * 6148 * @hide 6149 */ createHeadsUpContentView(boolean increasedHeight)6150 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 6151 if (useExistingRemoteView(mN.headsUpContentView)) { 6152 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 6153 ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) 6154 : mN.headsUpContentView; 6155 } else if (mStyle != null) { 6156 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 6157 if (styleView != null) { 6158 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 6159 ? minimallyDecoratedHeadsUpContentView(styleView) : styleView; 6160 } 6161 } else if (mActions.size() == 0) { 6162 return null; 6163 } 6164 6165 // We only want at most a single remote input history to be shown here, otherwise 6166 // the content would become squished. 6167 StandardTemplateParams p = mParams.reset() 6168 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 6169 .fillTextsFrom(this) 6170 .setMaxRemoteInputHistory(1); 6171 return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, 6172 null /* result */); 6173 } 6174 6175 /** 6176 * Construct a RemoteViews for the final heads-up notification layout. 6177 */ createHeadsUpContentView()6178 public RemoteViews createHeadsUpContentView() { 6179 return createHeadsUpContentView(false /* useIncreasedHeight */); 6180 } 6181 6182 /** 6183 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 6184 * 6185 * @param isLowPriority is this notification low priority 6186 * @hide 6187 */ 6188 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)6189 public RemoteViews makePublicContentView(boolean isLowPriority) { 6190 if (mN.publicVersion != null) { 6191 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 6192 return builder.createContentView(); 6193 } 6194 Bundle savedBundle = mN.extras; 6195 Style style = mStyle; 6196 mStyle = null; 6197 Icon largeIcon = mN.mLargeIcon; 6198 mN.mLargeIcon = null; 6199 Bitmap largeIconLegacy = mN.largeIcon; 6200 mN.largeIcon = null; 6201 ArrayList<Action> actions = mActions; 6202 mActions = new ArrayList<>(); 6203 Bundle publicExtras = new Bundle(); 6204 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 6205 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 6206 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 6207 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 6208 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 6209 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 6210 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 6211 if (appName != null) { 6212 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 6213 } 6214 mN.extras = publicExtras; 6215 RemoteViews view; 6216 StandardTemplateParams params = mParams.reset() 6217 .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC) 6218 .fillTextsFrom(this); 6219 if (isLowPriority) { 6220 params.highlightExpander(false); 6221 } 6222 view = makeNotificationHeader(params); 6223 view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true); 6224 mN.extras = savedBundle; 6225 mN.mLargeIcon = largeIcon; 6226 mN.largeIcon = largeIconLegacy; 6227 mActions = actions; 6228 mStyle = style; 6229 return view; 6230 } 6231 6232 /** 6233 * Construct a content view for the display when low - priority 6234 * 6235 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise 6236 * a new subtext is created consisting of the content of the 6237 * notification. 6238 * @hide 6239 */ makeLowPriorityContentView(boolean useRegularSubtext)6240 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { 6241 StandardTemplateParams p = mParams.reset() 6242 .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED) 6243 .highlightExpander(false) 6244 .fillTextsFrom(this); 6245 if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) { 6246 p.summaryText(createSummaryText()); 6247 } 6248 RemoteViews header = makeNotificationHeader(p); 6249 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true); 6250 // The low priority header has no app name and shows the text 6251 header.setBoolean(R.id.notification_header, "styleTextAsTitle", true); 6252 return header; 6253 } 6254 createSummaryText()6255 private CharSequence createSummaryText() { 6256 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE); 6257 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) { 6258 return titleText; 6259 } 6260 SpannableStringBuilder summary = new SpannableStringBuilder(); 6261 if (titleText == null) { 6262 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG); 6263 } 6264 BidiFormatter bidi = BidiFormatter.getInstance(); 6265 if (titleText != null) { 6266 summary.append(bidi.unicodeWrap(titleText)); 6267 } 6268 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT); 6269 if (titleText != null && contentText != null) { 6270 summary.append(bidi.unicodeWrap(mContext.getText( 6271 R.string.notification_header_divider_symbol_with_spaces))); 6272 } 6273 if (contentText != null) { 6274 summary.append(bidi.unicodeWrap(contentText)); 6275 } 6276 return summary; 6277 } 6278 generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6279 private RemoteViews generateActionButton(Action action, boolean emphasizedMode, 6280 StandardTemplateParams p) { 6281 final boolean tombstone = (action.actionIntent == null); 6282 final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 6283 getActionButtonLayoutResource(emphasizedMode, tombstone)); 6284 if (!tombstone) { 6285 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 6286 } 6287 button.setContentDescription(R.id.action0, action.title); 6288 if (action.mRemoteInputs != null) { 6289 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 6290 } 6291 if (emphasizedMode) { 6292 // change the background bgColor 6293 CharSequence title = action.title; 6294 int buttonFillColor = getColors(p).getSecondaryAccentColor(); 6295 if (tombstone) { 6296 buttonFillColor = setAlphaComponentByFloatDimen(mContext, 6297 ContrastColorUtil.resolveSecondaryColor( 6298 mContext, getColors(p).getBackgroundColor(), mInNightMode), 6299 R.dimen.notification_action_disabled_container_alpha); 6300 } 6301 if (isLegacy()) { 6302 title = ContrastColorUtil.clearColorSpans(title); 6303 } else { 6304 // Check for a full-length span color to use as the button fill color. 6305 Integer fullLengthColor = getFullLengthSpanColor(title); 6306 if (fullLengthColor != null) { 6307 // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. 6308 int notifBackgroundColor = getColors(p).getBackgroundColor(); 6309 buttonFillColor = ensureButtonFillContrast( 6310 fullLengthColor, notifBackgroundColor); 6311 } 6312 // Remove full-length color spans and ensure text contrast with the button fill. 6313 title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor); 6314 } 6315 button.setTextViewText(R.id.action0, ensureColorSpanContrast(title, p)); 6316 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, 6317 buttonFillColor, mInNightMode); 6318 if (tombstone) { 6319 textColor = setAlphaComponentByFloatDimen(mContext, 6320 ContrastColorUtil.resolveSecondaryColor( 6321 mContext, getColors(p).getBackgroundColor(), mInNightMode), 6322 R.dimen.notification_action_disabled_content_alpha); 6323 } 6324 button.setTextColor(R.id.action0, textColor); 6325 // We only want about 20% alpha for the ripple 6326 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000; 6327 button.setColorStateList(R.id.action0, "setRippleColor", 6328 ColorStateList.valueOf(rippleColor)); 6329 button.setColorStateList(R.id.action0, "setButtonBackground", 6330 ColorStateList.valueOf(buttonFillColor)); 6331 if (p.mCallStyleActions) { 6332 button.setImageViewIcon(R.id.action0, action.getIcon()); 6333 boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY); 6334 button.setBoolean(R.id.action0, "setIsPriority", priority); 6335 int minWidthDimen = 6336 priority ? R.dimen.call_notification_system_action_min_width : 0; 6337 button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen); 6338 } 6339 } else { 6340 button.setTextViewText(R.id.action0, ensureColorSpanContrast( 6341 action.title, p)); 6342 button.setTextColor(R.id.action0, getStandardActionColor(p)); 6343 } 6344 // CallStyle notifications add action buttons which don't actually exist in mActions, 6345 // so we have to omit the index in that case. 6346 int actionIndex = mActions.indexOf(action); 6347 if (actionIndex != -1) { 6348 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex); 6349 } 6350 return button; 6351 } 6352 getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone)6353 private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) { 6354 if (emphasizedMode) { 6355 return tombstone ? getEmphasizedTombstoneActionLayoutResource() 6356 : getEmphasizedActionLayoutResource(); 6357 } else { 6358 return tombstone ? getActionTombstoneLayoutResource() 6359 : getActionLayoutResource(); 6360 } 6361 } 6362 6363 /** 6364 * Set the alpha component of {@code color} to be {@code alphaDimenResId}. 6365 */ setAlphaComponentByFloatDimen(Context context, @ColorInt int color, @DimenRes int alphaDimenResId)6366 private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color, 6367 @DimenRes int alphaDimenResId) { 6368 final TypedValue alphaValue = new TypedValue(); 6369 context.getResources().getValue(alphaDimenResId, alphaValue, true); 6370 return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255)); 6371 } 6372 6373 /** 6374 * Extract the color from a full-length span from the text. 6375 * 6376 * @param charSequence the charSequence containing spans 6377 * @return the raw color of the text's last full-length span containing a color, or null if 6378 * no full-length span sets the text color. 6379 * @hide 6380 */ 6381 @VisibleForTesting 6382 @Nullable getFullLengthSpanColor(CharSequence charSequence)6383 public static Integer getFullLengthSpanColor(CharSequence charSequence) { 6384 // NOTE: this method preserves the functionality that for a CharSequence with multiple 6385 // full-length spans, the color of the last one is used. 6386 Integer result = null; 6387 if (charSequence instanceof Spanned) { 6388 Spanned ss = (Spanned) charSequence; 6389 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 6390 // First read through all full-length spans to get the button fill color, which will 6391 // be used as the background color for ensuring contrast of non-full-length spans. 6392 for (Object span : spans) { 6393 int spanStart = ss.getSpanStart(span); 6394 int spanEnd = ss.getSpanEnd(span); 6395 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 6396 if (!fullLength) { 6397 continue; 6398 } 6399 if (span instanceof TextAppearanceSpan) { 6400 TextAppearanceSpan originalSpan = (TextAppearanceSpan) span; 6401 ColorStateList textColor = originalSpan.getTextColor(); 6402 if (textColor != null) { 6403 result = textColor.getDefaultColor(); 6404 } 6405 } else if (span instanceof ForegroundColorSpan) { 6406 ForegroundColorSpan originalSpan = (ForegroundColorSpan) span; 6407 result = originalSpan.getForegroundColor(); 6408 } 6409 } 6410 } 6411 return result; 6412 } 6413 6414 /** 6415 * Ensures contrast on color spans against a background color. 6416 * Note that any full-length color spans will be removed instead of being contrasted. 6417 * 6418 * @hide 6419 */ 6420 @VisibleForTesting ensureColorSpanContrast(CharSequence charSequence, StandardTemplateParams p)6421 public CharSequence ensureColorSpanContrast(CharSequence charSequence, 6422 StandardTemplateParams p) { 6423 return ContrastColorUtil.ensureColorSpanContrast(charSequence, getBackgroundColor(p)); 6424 } 6425 6426 /** 6427 * Determines if the color is light or dark. Specifically, this is using the same metric as 6428 * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that 6429 * the direction of color shift is consistent. 6430 * 6431 * @param color the color to check 6432 * @return true if the color has higher contrast with white than black 6433 * @hide 6434 */ isColorDark(int color)6435 public static boolean isColorDark(int color) { 6436 // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint. 6437 return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474; 6438 } 6439 6440 /** 6441 * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue 6442 * as the original color, but is lightened or darkened depending on whether the background 6443 * is dark or light. 6444 * 6445 * @hide 6446 */ 6447 @VisibleForTesting ensureButtonFillContrast(int color, int bg)6448 public static int ensureButtonFillContrast(int color, int bg) { 6449 return isColorDark(bg) 6450 ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3) 6451 : ContrastColorUtil.findContrastColor(color, bg, true, 1.3); 6452 } 6453 6454 6455 /** 6456 * @return Whether we are currently building a notification from a legacy (an app that 6457 * doesn't create material notifications by itself) app. 6458 */ isLegacy()6459 private boolean isLegacy() { 6460 if (!mIsLegacyInitialized) { 6461 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion 6462 < Build.VERSION_CODES.LOLLIPOP; 6463 mIsLegacyInitialized = true; 6464 } 6465 return mIsLegacy; 6466 } 6467 6468 private CharSequence processLegacyText(CharSequence charSequence) { 6469 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion(); 6470 if (isAlreadyLightText) { 6471 return getColorUtil().invertCharSequenceColors(charSequence); 6472 } else { 6473 return charSequence; 6474 } 6475 } 6476 6477 /** 6478 * Apply any necessary colors to the small icon 6479 */ 6480 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, 6481 StandardTemplateParams p) { 6482 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); 6483 int color = getSmallIconColor(p); 6484 contentView.setInt(R.id.icon, "setBackgroundColor", 6485 getBackgroundColor(p)); 6486 contentView.setInt(R.id.icon, "setOriginalIconColor", 6487 colorable ? color : COLOR_INVALID); 6488 } 6489 6490 /** 6491 * Make the largeIcon dark if it's a fake smallIcon (that is, 6492 * if it's grayscale). 6493 */ 6494 // TODO: also check bounds, transparency, that sort of thing. 6495 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView, 6496 StandardTemplateParams p) { 6497 if (largeIcon != null && isLegacy() 6498 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 6499 // resolve color will fall back to the default when legacy 6500 int color = getSmallIconColor(p); 6501 contentView.setInt(R.id.icon, "setOriginalIconColor", color); 6502 } 6503 } 6504 6505 private void sanitizeColor() { 6506 if (mN.color != COLOR_DEFAULT) { 6507 mN.color |= 0xFF000000; // no alpha for custom colors 6508 } 6509 } 6510 6511 /** 6512 * Gets the standard action button color 6513 */ 6514 private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { 6515 return mTintActionButtons || isBackgroundColorized(p) 6516 ? getPrimaryAccentColor(p) : getSecondaryTextColor(p); 6517 } 6518 6519 /** 6520 * Gets the foreground color of the small icon. If the notification is colorized, this 6521 * is the primary text color, otherwise it's the contrast-adjusted app-provided color. 6522 */ 6523 private @ColorInt int getSmallIconColor(StandardTemplateParams p) { 6524 return getColors(p).getContrastColor(); 6525 } 6526 6527 /** @return the theme's accent color for colored UI elements. */ 6528 private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) { 6529 return getColors(p).getPrimaryAccentColor(); 6530 } 6531 6532 /** 6533 * Apply the unstyled operations and return a new {@link Notification} object. 6534 * @hide 6535 */ 6536 @NonNull 6537 public Notification buildUnstyled() { 6538 if (mActions.size() > 0) { 6539 mN.actions = new Action[mActions.size()]; 6540 mActions.toArray(mN.actions); 6541 } 6542 if (!mPersonList.isEmpty()) { 6543 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); 6544 } 6545 if (mN.bigContentView != null || mN.contentView != null 6546 || mN.headsUpContentView != null) { 6547 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 6548 } 6549 return mN; 6550 } 6551 6552 /** 6553 * Creates a Builder from an existing notification so further changes can be made. 6554 * @param context The context for your application / activity. 6555 * @param n The notification to create a Builder from. 6556 */ 6557 @NonNull recoverBuilder(Context context, Notification n)6558 public static Notification.Builder recoverBuilder(Context context, Notification n) { 6559 // Re-create notification context so we can access app resources. 6560 ApplicationInfo applicationInfo = n.extras.getParcelable( 6561 EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class); 6562 Context builderContext; 6563 if (applicationInfo != null) { 6564 try { 6565 builderContext = context.createApplicationContext(applicationInfo, 6566 Context.CONTEXT_RESTRICTED); 6567 } catch (NameNotFoundException e) { 6568 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 6569 builderContext = context; // try with our context 6570 } 6571 } else { 6572 builderContext = context; // try with given context 6573 } 6574 6575 return new Builder(builderContext, n); 6576 } 6577 6578 /** 6579 * Determines whether the platform can generate contextual actions for a notification. 6580 * By default this is true. 6581 */ 6582 @NonNull setAllowSystemGeneratedContextualActions(boolean allowed)6583 public Builder setAllowSystemGeneratedContextualActions(boolean allowed) { 6584 mN.mAllowSystemGeneratedContextualActions = allowed; 6585 return this; 6586 } 6587 6588 /** 6589 * @deprecated Use {@link #build()} instead. 6590 */ 6591 @Deprecated getNotification()6592 public Notification getNotification() { 6593 return build(); 6594 } 6595 6596 /** 6597 * Combine all of the options that have been set and return a new {@link Notification} 6598 * object. 6599 * 6600 * If this notification has {@link BubbleMetadata} attached that was created with 6601 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 6602 * metadata matches the shortcutId set on the notification builder, if one was set. 6603 * If the shortcutId's were specified but do not match, an exception is thrown here. 6604 * 6605 * @see BubbleMetadata.Builder#Builder(String) 6606 * @see #setShortcutId(String) 6607 */ 6608 @NonNull build()6609 public Notification build() { 6610 // Check shortcut id matches 6611 if (mN.mShortcutId != null 6612 && mN.mBubbleMetadata != null 6613 && mN.mBubbleMetadata.getShortcutId() != null 6614 && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { 6615 throw new IllegalArgumentException( 6616 "Notification and BubbleMetadata shortcut id's don't match," 6617 + " notification: " + mN.mShortcutId 6618 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); 6619 } 6620 6621 // Adds any new extras provided by the user. 6622 if (mUserExtras != null) { 6623 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 6624 if (SystemProperties.getBoolean( 6625 "persist.sysui.notification.builder_extras_override", false)) { 6626 mN.extras.putAll(saveExtras); 6627 } else { 6628 saveExtras.putAll(mN.extras); 6629 mN.extras = saveExtras; 6630 } 6631 } 6632 6633 mN.creationTime = System.currentTimeMillis(); 6634 6635 // lazy stuff from mContext; see comment in Builder(Context, Notification) 6636 Notification.addFieldsFromContext(mContext, mN); 6637 6638 buildUnstyled(); 6639 6640 if (mStyle != null) { 6641 mStyle.reduceImageSizes(mContext); 6642 mStyle.purgeResources(); 6643 mStyle.validate(mContext); 6644 mStyle.buildStyled(mN); 6645 } 6646 mN.reduceImageSizes(mContext); 6647 6648 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 6649 && !styleDisplaysCustomViewInline()) { 6650 RemoteViews newContentView = mN.contentView; 6651 RemoteViews newBigContentView = mN.bigContentView; 6652 RemoteViews newHeadsUpContentView = mN.headsUpContentView; 6653 if (newContentView == null) { 6654 newContentView = createContentView(); 6655 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 6656 newContentView.getSequenceNumber()); 6657 } 6658 if (newBigContentView == null) { 6659 newBigContentView = createBigContentView(); 6660 if (newBigContentView != null) { 6661 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 6662 newBigContentView.getSequenceNumber()); 6663 } 6664 } 6665 if (newHeadsUpContentView == null) { 6666 newHeadsUpContentView = createHeadsUpContentView(); 6667 if (newHeadsUpContentView != null) { 6668 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 6669 newHeadsUpContentView.getSequenceNumber()); 6670 } 6671 } 6672 // Don't set any of the content views until after they have all been generated, 6673 // to avoid the generated .contentView triggering the logic which skips generating 6674 // the .bigContentView. 6675 mN.contentView = newContentView; 6676 mN.bigContentView = newBigContentView; 6677 mN.headsUpContentView = newHeadsUpContentView; 6678 } 6679 6680 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 6681 mN.flags |= FLAG_SHOW_LIGHTS; 6682 } 6683 6684 mN.allPendingIntents = null; 6685 6686 return mN; 6687 } 6688 styleDisplaysCustomViewInline()6689 private boolean styleDisplaysCustomViewInline() { 6690 return mStyle != null && mStyle.displayCustomViewInline(); 6691 } 6692 6693 /** 6694 * Apply this Builder to an existing {@link Notification} object. 6695 * 6696 * @hide 6697 */ 6698 @NonNull buildInto(@onNull Notification n)6699 public Notification buildInto(@NonNull Notification n) { 6700 build().cloneInto(n, true); 6701 return n; 6702 } 6703 6704 /** 6705 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 6706 * change. 6707 * 6708 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 6709 * 6710 * @hide 6711 */ maybeCloneStrippedForDelivery(Notification n)6712 public static Notification maybeCloneStrippedForDelivery(Notification n) { 6713 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 6714 6715 // Only strip views for known Styles because we won't know how to 6716 // re-create them otherwise. 6717 if (!TextUtils.isEmpty(templateClass) 6718 && getNotificationStyleClass(templateClass) == null) { 6719 return n; 6720 } 6721 6722 // Only strip unmodified BuilderRemoteViews. 6723 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 6724 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 6725 n.contentView.getSequenceNumber(); 6726 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 6727 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 6728 n.bigContentView.getSequenceNumber(); 6729 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 6730 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 6731 n.headsUpContentView.getSequenceNumber(); 6732 6733 // Nothing to do here, no need to clone. 6734 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 6735 return n; 6736 } 6737 6738 Notification clone = n.clone(); 6739 if (stripContentView) { 6740 clone.contentView = null; 6741 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 6742 } 6743 if (stripBigContentView) { 6744 clone.bigContentView = null; 6745 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 6746 } 6747 if (stripHeadsUpContentView) { 6748 clone.headsUpContentView = null; 6749 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 6750 } 6751 return clone; 6752 } 6753 6754 @UnsupportedAppUsage getBaseLayoutResource()6755 private int getBaseLayoutResource() { 6756 return R.layout.notification_template_material_base; 6757 } 6758 getHeadsUpBaseLayoutResource()6759 private int getHeadsUpBaseLayoutResource() { 6760 return R.layout.notification_template_material_heads_up_base; 6761 } 6762 getBigBaseLayoutResource()6763 private int getBigBaseLayoutResource() { 6764 return R.layout.notification_template_material_big_base; 6765 } 6766 getBigPictureLayoutResource()6767 private int getBigPictureLayoutResource() { 6768 return R.layout.notification_template_material_big_picture; 6769 } 6770 getBigTextLayoutResource()6771 private int getBigTextLayoutResource() { 6772 return R.layout.notification_template_material_big_text; 6773 } 6774 getInboxLayoutResource()6775 private int getInboxLayoutResource() { 6776 return R.layout.notification_template_material_inbox; 6777 } 6778 getMessagingLayoutResource()6779 private int getMessagingLayoutResource() { 6780 return R.layout.notification_template_material_messaging; 6781 } 6782 getBigMessagingLayoutResource()6783 private int getBigMessagingLayoutResource() { 6784 return R.layout.notification_template_material_big_messaging; 6785 } 6786 getConversationLayoutResource()6787 private int getConversationLayoutResource() { 6788 return R.layout.notification_template_material_conversation; 6789 } 6790 getActionLayoutResource()6791 private int getActionLayoutResource() { 6792 return R.layout.notification_material_action; 6793 } 6794 getEmphasizedActionLayoutResource()6795 private int getEmphasizedActionLayoutResource() { 6796 return R.layout.notification_material_action_emphasized; 6797 } 6798 getEmphasizedTombstoneActionLayoutResource()6799 private int getEmphasizedTombstoneActionLayoutResource() { 6800 return R.layout.notification_material_action_emphasized_tombstone; 6801 } 6802 getActionTombstoneLayoutResource()6803 private int getActionTombstoneLayoutResource() { 6804 return R.layout.notification_material_action_tombstone; 6805 } 6806 getBackgroundColor(StandardTemplateParams p)6807 private @ColorInt int getBackgroundColor(StandardTemplateParams p) { 6808 return getColors(p).getBackgroundColor(); 6809 } 6810 textColorsNeedInversion()6811 private boolean textColorsNeedInversion() { 6812 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { 6813 return false; 6814 } 6815 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 6816 return targetSdkVersion > Build.VERSION_CODES.M 6817 && targetSdkVersion < Build.VERSION_CODES.O; 6818 } 6819 6820 /** 6821 * Get the text that should be displayed in the statusBar when heads upped. This is 6822 * usually just the app name, but may be different depending on the style. 6823 * 6824 * @param publicMode If true, return a text that is safe to display in public. 6825 * 6826 * @hide 6827 */ getHeadsUpStatusBarText(boolean publicMode)6828 public CharSequence getHeadsUpStatusBarText(boolean publicMode) { 6829 if (mStyle != null && !publicMode) { 6830 CharSequence text = mStyle.getHeadsUpStatusBarText(); 6831 if (!TextUtils.isEmpty(text)) { 6832 return text; 6833 } 6834 } 6835 return loadHeaderAppName(); 6836 } 6837 6838 /** 6839 * @return if this builder uses a template 6840 * 6841 * @hide 6842 */ usesTemplate()6843 public boolean usesTemplate() { 6844 return (mN.contentView == null && mN.headsUpContentView == null 6845 && mN.bigContentView == null) 6846 || styleDisplaysCustomViewInline(); 6847 } 6848 } 6849 6850 /** 6851 * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom 6852 * remote views. 6853 * 6854 * @hide 6855 */ reduceImageSizes(Context context)6856 void reduceImageSizes(Context context) { 6857 if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) { 6858 return; 6859 } 6860 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6861 6862 if (mSmallIcon != null 6863 // Only bitmap icons can be downscaled. 6864 && (mSmallIcon.getType() == Icon.TYPE_BITMAP 6865 || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) { 6866 Resources resources = context.getResources(); 6867 int maxSize = resources.getDimensionPixelSize( 6868 isLowRam ? R.dimen.notification_small_icon_size_low_ram 6869 : R.dimen.notification_small_icon_size); 6870 mSmallIcon.scaleDownIfNecessary(maxSize, maxSize); 6871 } 6872 6873 if (mLargeIcon != null || largeIcon != null) { 6874 Resources resources = context.getResources(); 6875 Class<? extends Style> style = getNotificationStyle(); 6876 int maxSize = resources.getDimensionPixelSize(isLowRam 6877 ? R.dimen.notification_right_icon_size_low_ram 6878 : R.dimen.notification_right_icon_size); 6879 if (mLargeIcon != null) { 6880 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize); 6881 } 6882 if (largeIcon != null) { 6883 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize); 6884 } 6885 } 6886 reduceImageSizesForRemoteView(contentView, context, isLowRam); 6887 reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam); 6888 reduceImageSizesForRemoteView(bigContentView, context, isLowRam); 6889 extras.putBoolean(EXTRA_REDUCED_IMAGES, true); 6890 } 6891 reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6892 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, 6893 boolean isLowRam) { 6894 if (remoteView != null) { 6895 Resources resources = context.getResources(); 6896 int maxWidth = resources.getDimensionPixelSize(isLowRam 6897 ? R.dimen.notification_custom_view_max_image_width_low_ram 6898 : R.dimen.notification_custom_view_max_image_width); 6899 int maxHeight = resources.getDimensionPixelSize(isLowRam 6900 ? R.dimen.notification_custom_view_max_image_height_low_ram 6901 : R.dimen.notification_custom_view_max_image_height); 6902 remoteView.reduceImageSizes(maxWidth, maxHeight); 6903 } 6904 } 6905 6906 /** 6907 * @return whether this notification is a foreground service notification 6908 * @hide 6909 */ isForegroundService()6910 public boolean isForegroundService() { 6911 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 6912 } 6913 6914 /** 6915 * @return whether this notification is associated with a user initiated job 6916 * @hide 6917 */ 6918 @TestApi isUserInitiatedJob()6919 public boolean isUserInitiatedJob() { 6920 return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0; 6921 } 6922 6923 /** 6924 * @return whether this notification is associated with either a foreground service or 6925 * a user initiated job 6926 * @hide 6927 */ isFgsOrUij()6928 public boolean isFgsOrUij() { 6929 return isForegroundService() || isUserInitiatedJob(); 6930 } 6931 6932 /** 6933 * Describe whether this notification's content such that it should always display 6934 * immediately when tied to a foreground service, even if the system might generally 6935 * avoid showing the notifications for short-lived foreground service lifetimes. 6936 * 6937 * Immediate visibility of the Notification is indicated when: 6938 * <ul> 6939 * <li>The app specifically indicated it with 6940 * {@link Notification.Builder#setForegroundServiceBehavior(int) 6941 * setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li> 6942 * <li>It is a media notification or has an associated media session</li> 6943 * <li>It is a call or navigation notification</li> 6944 * <li>It provides additional action affordances</li> 6945 * </ul> 6946 * 6947 * If the app has specified 6948 * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)} 6949 * then this method will return {@code false} and notification visibility will be 6950 * deferred following the service's transition to the foreground state even in the 6951 * circumstances described above. 6952 * 6953 * @return whether this notification should be displayed immediately when 6954 * its associated service transitions to the foreground state 6955 * @hide 6956 */ 6957 @TestApi shouldShowForegroundImmediately()6958 public boolean shouldShowForegroundImmediately() { 6959 // Has the app demanded immediate display? 6960 if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) { 6961 return true; 6962 } 6963 6964 // Has the app demanded deferred display? 6965 if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) { 6966 return false; 6967 } 6968 6969 // We show these sorts of notifications immediately in the absence of 6970 // any explicit app declaration 6971 if (isMediaNotification() 6972 || CATEGORY_CALL.equals(category) 6973 || CATEGORY_NAVIGATION.equals(category) 6974 || (actions != null && actions.length > 0)) { 6975 return true; 6976 } 6977 6978 // No extenuating circumstances: defer visibility 6979 return false; 6980 } 6981 6982 /** 6983 * Has forced deferral for FGS purposes been specified? 6984 * @hide 6985 */ isForegroundDisplayForceDeferred()6986 public boolean isForegroundDisplayForceDeferred() { 6987 return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior; 6988 } 6989 6990 /** 6991 * @return the style class of this notification 6992 * @hide 6993 */ getNotificationStyle()6994 public Class<? extends Notification.Style> getNotificationStyle() { 6995 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6996 6997 if (!TextUtils.isEmpty(templateClass)) { 6998 return Notification.getNotificationStyleClass(templateClass); 6999 } 7000 return null; 7001 } 7002 7003 /** 7004 * @return whether the style of this notification is the one provided 7005 * @hide 7006 */ isStyle(@onNull Class<? extends Style> styleClass)7007 public boolean isStyle(@NonNull Class<? extends Style> styleClass) { 7008 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 7009 return Objects.equals(templateClass, styleClass.getName()); 7010 } 7011 7012 /** 7013 * @return true if this notification is colorized *for the purposes of ranking*. If the 7014 * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual 7015 * appearance of the notification may not be "colorized". 7016 * 7017 * @hide 7018 */ isColorized()7019 public boolean isColorized() { 7020 return extras.getBoolean(EXTRA_COLORIZED) 7021 && (hasColorizedPermission() || isFgsOrUij()); 7022 } 7023 7024 /** 7025 * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS 7026 * permission. The permission is checked when a notification is enqueued. 7027 * 7028 * @hide 7029 */ hasColorizedPermission()7030 public boolean hasColorizedPermission() { 7031 return (flags & Notification.FLAG_CAN_COLORIZE) != 0; 7032 } 7033 7034 /** 7035 * @return true if this is a media style notification with a media session 7036 * 7037 * @hide 7038 */ isMediaNotification()7039 public boolean isMediaNotification() { 7040 Class<? extends Style> style = getNotificationStyle(); 7041 boolean isMediaStyle = (MediaStyle.class.equals(style) 7042 || DecoratedMediaCustomViewStyle.class.equals(style)); 7043 7044 boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION, 7045 MediaSession.Token.class) != null; 7046 7047 return isMediaStyle && hasMediaSession; 7048 } 7049 7050 /** 7051 * @return true for custom notifications, including notifications 7052 * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle, 7053 * and other notifications with user-provided custom views. 7054 * 7055 * @hide 7056 */ isCustomNotification()7057 public Boolean isCustomNotification() { 7058 if (contentView == null 7059 && bigContentView == null 7060 && headsUpContentView == null) { 7061 return false; 7062 } 7063 return true; 7064 } 7065 7066 /** 7067 * @return true if this notification is showing as a bubble 7068 * 7069 * @hide 7070 */ isBubbleNotification()7071 public boolean isBubbleNotification() { 7072 return (flags & Notification.FLAG_BUBBLE) != 0; 7073 } 7074 hasLargeIcon()7075 private boolean hasLargeIcon() { 7076 return mLargeIcon != null || largeIcon != null; 7077 } 7078 7079 /** 7080 * @return true if the notification will show the time; false otherwise 7081 * @hide 7082 */ showsTime()7083 public boolean showsTime() { 7084 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 7085 } 7086 7087 /** 7088 * @return true if the notification will show a chronometer; false otherwise 7089 * @hide 7090 */ showsChronometer()7091 public boolean showsChronometer() { 7092 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 7093 } 7094 7095 /** 7096 * @return true if the notification has image 7097 */ hasImage()7098 public boolean hasImage() { 7099 if (isStyle(MessagingStyle.class) && extras != null) { 7100 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, 7101 Parcelable.class); 7102 if (!ArrayUtils.isEmpty(messages)) { 7103 for (MessagingStyle.Message m : MessagingStyle.Message 7104 .getMessagesFromBundleArray(messages)) { 7105 if (m.getDataUri() != null 7106 && m.getDataMimeType() != null 7107 && m.getDataMimeType().startsWith("image/")) { 7108 return true; 7109 } 7110 } 7111 } 7112 } else if (hasLargeIcon()) { 7113 return true; 7114 } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 7115 return true; 7116 } 7117 return false; 7118 } 7119 7120 7121 /** 7122 * @removed 7123 */ 7124 @SystemApi getNotificationStyleClass(String templateClass)7125 public static Class<? extends Style> getNotificationStyleClass(String templateClass) { 7126 for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) { 7127 if (templateClass.equals(innerClass.getName())) { 7128 return innerClass; 7129 } 7130 } 7131 return null; 7132 } 7133 buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)7134 private static void buildCustomContentIntoTemplate(@NonNull Context context, 7135 @NonNull RemoteViews template, @Nullable RemoteViews customContent, 7136 @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) { 7137 int childIndex = -1; 7138 if (customContent != null) { 7139 // Need to clone customContent before adding, because otherwise it can no longer be 7140 // parceled independently of remoteViews. 7141 customContent = customContent.clone(); 7142 if (p.mHeaderless) { 7143 template.removeFromParent(R.id.notification_top_line); 7144 // We do not know how many lines ar emote view has, so we presume it has 2; this 7145 // ensures that we don't under-pad the content, which could lead to abuse, at the 7146 // cost of making single-line custom content over-padded. 7147 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */); 7148 } else { 7149 // also update the end margin to account for the large icon or expander 7150 Resources resources = context.getResources(); 7151 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column, 7152 resources.getDimension(R.dimen.notification_content_margin_end) 7153 / resources.getDisplayMetrics().density); 7154 } 7155 template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 7156 template.addView(R.id.notification_main_column, customContent, 0 /* index */); 7157 template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 7158 childIndex = 0; 7159 } 7160 template.setIntTag(R.id.notification_main_column, 7161 com.android.internal.R.id.notification_custom_view_index_tag, 7162 childIndex); 7163 } 7164 7165 /** 7166 * An object that can apply a rich notification style to a {@link Notification.Builder} 7167 * object. 7168 */ 7169 public static abstract class Style { 7170 7171 /** 7172 * The number of items allowed simulatanously in the remote input history. 7173 * @hide 7174 */ 7175 static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3; 7176 private CharSequence mBigContentTitle; 7177 7178 /** 7179 * @hide 7180 */ 7181 protected CharSequence mSummaryText = null; 7182 7183 /** 7184 * @hide 7185 */ 7186 protected boolean mSummaryTextSet = false; 7187 7188 protected Builder mBuilder; 7189 7190 /** 7191 * Overrides ContentTitle in the big form of the template. 7192 * This defaults to the value passed to setContentTitle(). 7193 */ internalSetBigContentTitle(CharSequence title)7194 protected void internalSetBigContentTitle(CharSequence title) { 7195 mBigContentTitle = title; 7196 } 7197 7198 /** 7199 * Set the first line of text after the detail section in the big form of the template. 7200 */ internalSetSummaryText(CharSequence cs)7201 protected void internalSetSummaryText(CharSequence cs) { 7202 mSummaryText = cs; 7203 mSummaryTextSet = true; 7204 } 7205 setBuilder(Builder builder)7206 public void setBuilder(Builder builder) { 7207 if (mBuilder != builder) { 7208 mBuilder = builder; 7209 if (mBuilder != null) { 7210 mBuilder.setStyle(this); 7211 } 7212 } 7213 } 7214 checkBuilder()7215 protected void checkBuilder() { 7216 if (mBuilder == null) { 7217 throw new IllegalArgumentException("Style requires a valid Builder object"); 7218 } 7219 } 7220 getStandardView(int layoutId)7221 protected RemoteViews getStandardView(int layoutId) { 7222 // TODO(jeffdq): set the view type based on the layout resource? 7223 StandardTemplateParams p = mBuilder.mParams.reset() 7224 .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED) 7225 .fillTextsFrom(mBuilder); 7226 return getStandardView(layoutId, p, null); 7227 } 7228 7229 7230 /** 7231 * Get the standard view for this style. 7232 * 7233 * @param layoutId The layout id to use. 7234 * @param p the params for this inflation. 7235 * @param result The result where template bind information is saved. 7236 * @return A remoteView for this style. 7237 * @hide 7238 */ getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7239 protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p, 7240 TemplateBindResult result) { 7241 checkBuilder(); 7242 7243 if (mBigContentTitle != null) { 7244 p.mTitle = mBigContentTitle; 7245 } 7246 7247 return mBuilder.applyStandardTemplateWithActions(layoutId, p, result); 7248 } 7249 7250 /** 7251 * Construct a Style-specific RemoteViews for the collapsed notification layout. 7252 * The default implementation has nothing additional to add. 7253 * 7254 * @param increasedHeight true if this layout be created with an increased height. 7255 * @hide 7256 */ makeContentView(boolean increasedHeight)7257 public RemoteViews makeContentView(boolean increasedHeight) { 7258 return null; 7259 } 7260 7261 /** 7262 * Construct a Style-specific RemoteViews for the final big notification layout. 7263 * @hide 7264 */ makeBigContentView()7265 public RemoteViews makeBigContentView() { 7266 return null; 7267 } 7268 7269 /** 7270 * Construct a Style-specific RemoteViews for the final HUN layout. 7271 * 7272 * @param increasedHeight true if this layout be created with an increased height. 7273 * @hide 7274 */ makeHeadsUpContentView(boolean increasedHeight)7275 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7276 return null; 7277 } 7278 7279 /** 7280 * Apply any style-specific extras to this notification before shipping it out. 7281 * @hide 7282 */ addExtras(Bundle extras)7283 public void addExtras(Bundle extras) { 7284 if (mSummaryTextSet) { 7285 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 7286 } 7287 if (mBigContentTitle != null) { 7288 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 7289 } 7290 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 7291 } 7292 7293 /** 7294 * Reconstruct the internal state of this Style object from extras. 7295 * @hide 7296 */ restoreFromExtras(Bundle extras)7297 protected void restoreFromExtras(Bundle extras) { 7298 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 7299 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 7300 mSummaryTextSet = true; 7301 } 7302 if (extras.containsKey(EXTRA_TITLE_BIG)) { 7303 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 7304 } 7305 } 7306 7307 7308 /** 7309 * @hide 7310 */ buildStyled(Notification wip)7311 public Notification buildStyled(Notification wip) { 7312 addExtras(wip.extras); 7313 return wip; 7314 } 7315 7316 /** 7317 * @hide 7318 */ purgeResources()7319 public void purgeResources() {} 7320 7321 /** 7322 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 7323 * attached to. 7324 * 7325 * @return the fully constructed Notification. 7326 */ build()7327 public Notification build() { 7328 checkBuilder(); 7329 return mBuilder.build(); 7330 } 7331 7332 /** 7333 * @hide 7334 * @return Whether we should put the summary be put into the notification header 7335 */ hasSummaryInHeader()7336 public boolean hasSummaryInHeader() { 7337 return true; 7338 } 7339 7340 /** 7341 * @hide 7342 * @return Whether custom content views are displayed inline in the style 7343 */ displayCustomViewInline()7344 public boolean displayCustomViewInline() { 7345 return false; 7346 } 7347 7348 /** 7349 * Reduces the image sizes contained in this style. 7350 * 7351 * @hide 7352 */ reduceImageSizes(Context context)7353 public void reduceImageSizes(Context context) { 7354 } 7355 7356 /** 7357 * Validate that this style was properly composed. This is called at build time. 7358 * @hide 7359 */ validate(Context context)7360 public void validate(Context context) { 7361 } 7362 7363 /** 7364 * @hide 7365 */ areNotificationsVisiblyDifferent(Style other)7366 public abstract boolean areNotificationsVisiblyDifferent(Style other); 7367 7368 /** 7369 * @return the text that should be displayed in the statusBar when heads-upped. 7370 * If {@code null} is returned, the default implementation will be used. 7371 * 7372 * @hide 7373 */ getHeadsUpStatusBarText()7374 public CharSequence getHeadsUpStatusBarText() { 7375 return null; 7376 } 7377 } 7378 7379 /** 7380 * Helper class for generating large-format notifications that include a large image attachment. 7381 * 7382 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 7383 * <pre class="prettyprint"> 7384 * Notification notif = new Notification.Builder(mContext) 7385 * .setContentTitle("New photo from " + sender.toString()) 7386 * .setContentText(subject) 7387 * .setSmallIcon(R.drawable.new_post) 7388 * .setLargeIcon(aBitmap) 7389 * .setStyle(new Notification.BigPictureStyle() 7390 * .bigPicture(aBigBitmap)) 7391 * .build(); 7392 * </pre> 7393 * 7394 * @see Notification#bigContentView 7395 */ 7396 public static class BigPictureStyle extends Style { 7397 private Icon mPictureIcon; 7398 private Icon mBigLargeIcon; 7399 private boolean mBigLargeIconSet = false; 7400 private CharSequence mPictureContentDescription; 7401 private boolean mShowBigPictureWhenCollapsed; 7402 BigPictureStyle()7403 public BigPictureStyle() { 7404 } 7405 7406 /** 7407 * @deprecated use {@code BigPictureStyle()}. 7408 */ 7409 @Deprecated BigPictureStyle(Builder builder)7410 public BigPictureStyle(Builder builder) { 7411 setBuilder(builder); 7412 } 7413 7414 /** 7415 * Overrides ContentTitle in the big form of the template. 7416 * This defaults to the value passed to setContentTitle(). 7417 */ 7418 @NonNull setBigContentTitle(@ullable CharSequence title)7419 public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) { 7420 internalSetBigContentTitle(safeCharSequence(title)); 7421 return this; 7422 } 7423 7424 /** 7425 * Set the first line of text after the detail section in the big form of the template. 7426 */ 7427 @NonNull setSummaryText(@ullable CharSequence cs)7428 public BigPictureStyle setSummaryText(@Nullable CharSequence cs) { 7429 internalSetSummaryText(safeCharSequence(cs)); 7430 return this; 7431 } 7432 7433 /** 7434 * Set the content description of the big picture. 7435 */ 7436 @NonNull setContentDescription( @ullable CharSequence contentDescription)7437 public BigPictureStyle setContentDescription( 7438 @Nullable CharSequence contentDescription) { 7439 mPictureContentDescription = contentDescription; 7440 return this; 7441 } 7442 7443 /** 7444 * @hide 7445 */ 7446 @Nullable getBigPicture()7447 public Icon getBigPicture() { 7448 if (mPictureIcon != null) { 7449 return mPictureIcon; 7450 } 7451 return null; 7452 } 7453 7454 /** 7455 * Provide the bitmap to be used as the payload for the BigPicture notification. 7456 */ 7457 @NonNull bigPicture(@ullable Bitmap b)7458 public BigPictureStyle bigPicture(@Nullable Bitmap b) { 7459 mPictureIcon = b == null ? null : Icon.createWithBitmap(b); 7460 return this; 7461 } 7462 7463 /** 7464 * Provide the content Uri to be used as the payload for the BigPicture notification. 7465 */ 7466 @NonNull bigPicture(@ullable Icon icon)7467 public BigPictureStyle bigPicture(@Nullable Icon icon) { 7468 mPictureIcon = icon; 7469 return this; 7470 } 7471 7472 /** 7473 * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and 7474 * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed 7475 * state of this notification. 7476 */ 7477 @NonNull showBigPictureWhenCollapsed(boolean show)7478 public BigPictureStyle showBigPictureWhenCollapsed(boolean show) { 7479 mShowBigPictureWhenCollapsed = show; 7480 return this; 7481 } 7482 7483 /** 7484 * Override the large icon when the big notification is shown. 7485 */ 7486 @NonNull bigLargeIcon(@ullable Bitmap b)7487 public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) { 7488 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 7489 } 7490 7491 /** 7492 * Override the large icon when the big notification is shown. 7493 */ 7494 @NonNull bigLargeIcon(@ullable Icon icon)7495 public BigPictureStyle bigLargeIcon(@Nullable Icon icon) { 7496 mBigLargeIconSet = true; 7497 mBigLargeIcon = icon; 7498 return this; 7499 } 7500 7501 /** @hide */ 7502 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 7503 7504 /** 7505 * @hide 7506 */ 7507 @Override purgeResources()7508 public void purgeResources() { 7509 super.purgeResources(); 7510 if (mPictureIcon != null) { 7511 mPictureIcon.convertToAshmem(); 7512 } 7513 if (mBigLargeIcon != null) { 7514 mBigLargeIcon.convertToAshmem(); 7515 } 7516 } 7517 7518 /** 7519 * @hide 7520 */ 7521 @Override reduceImageSizes(Context context)7522 public void reduceImageSizes(Context context) { 7523 super.reduceImageSizes(context); 7524 Resources resources = context.getResources(); 7525 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 7526 if (mPictureIcon != null) { 7527 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam 7528 ? R.dimen.notification_big_picture_max_height_low_ram 7529 : R.dimen.notification_big_picture_max_height); 7530 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam 7531 ? R.dimen.notification_big_picture_max_width_low_ram 7532 : R.dimen.notification_big_picture_max_width); 7533 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight); 7534 } 7535 if (mBigLargeIcon != null) { 7536 int rightIconSize = resources.getDimensionPixelSize(isLowRam 7537 ? R.dimen.notification_right_icon_size_low_ram 7538 : R.dimen.notification_right_icon_size); 7539 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 7540 } 7541 } 7542 7543 /** 7544 * @hide 7545 */ 7546 @Override makeContentView(boolean increasedHeight)7547 public RemoteViews makeContentView(boolean increasedHeight) { 7548 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7549 return super.makeContentView(increasedHeight); 7550 } 7551 7552 StandardTemplateParams p = mBuilder.mParams.reset() 7553 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 7554 .fillTextsFrom(mBuilder) 7555 .promotedPicture(mPictureIcon); 7556 return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */); 7557 } 7558 7559 /** 7560 * @hide 7561 */ 7562 @Override makeHeadsUpContentView(boolean increasedHeight)7563 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7564 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7565 return super.makeHeadsUpContentView(increasedHeight); 7566 } 7567 7568 StandardTemplateParams p = mBuilder.mParams.reset() 7569 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 7570 .fillTextsFrom(mBuilder) 7571 .promotedPicture(mPictureIcon); 7572 return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */); 7573 } 7574 7575 /** 7576 * @hide 7577 */ makeBigContentView()7578 public RemoteViews makeBigContentView() { 7579 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 7580 // This covers the following cases: 7581 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 7582 // mN.mLargeIcon 7583 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 7584 Icon oldLargeIcon = null; 7585 Bitmap largeIconLegacy = null; 7586 if (mBigLargeIconSet) { 7587 oldLargeIcon = mBuilder.mN.mLargeIcon; 7588 mBuilder.mN.mLargeIcon = mBigLargeIcon; 7589 // The legacy largeIcon might not allow us to clear the image, as it's taken in 7590 // replacement if the other one is null. Because we're restoring these legacy icons 7591 // for old listeners, this is in general non-null. 7592 largeIconLegacy = mBuilder.mN.largeIcon; 7593 mBuilder.mN.largeIcon = null; 7594 } 7595 7596 StandardTemplateParams p = mBuilder.mParams.reset() 7597 .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder); 7598 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), 7599 p, null /* result */); 7600 if (mSummaryTextSet) { 7601 contentView.setTextViewText(R.id.text, mBuilder.ensureColorSpanContrast( 7602 mBuilder.processLegacyText(mSummaryText), p)); 7603 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); 7604 contentView.setViewVisibility(R.id.text, View.VISIBLE); 7605 } 7606 7607 if (mBigLargeIconSet) { 7608 mBuilder.mN.mLargeIcon = oldLargeIcon; 7609 mBuilder.mN.largeIcon = largeIconLegacy; 7610 } 7611 7612 contentView.setImageViewIcon(R.id.big_picture, mPictureIcon); 7613 7614 if (mPictureContentDescription != null) { 7615 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription); 7616 } 7617 7618 return contentView; 7619 } 7620 7621 /** 7622 * @hide 7623 */ addExtras(Bundle extras)7624 public void addExtras(Bundle extras) { 7625 super.addExtras(extras); 7626 7627 if (mBigLargeIconSet) { 7628 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 7629 } 7630 if (mPictureContentDescription != null) { 7631 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION, 7632 mPictureContentDescription); 7633 } 7634 extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); 7635 7636 // If the icon contains a bitmap, use the old extra so that listeners which look for 7637 // that extra can still find the picture. Don't include the new extra in that case, 7638 // to avoid duplicating data. 7639 if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) { 7640 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); 7641 extras.putParcelable(EXTRA_PICTURE_ICON, null); 7642 } else { 7643 extras.putParcelable(EXTRA_PICTURE, null); 7644 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); 7645 } 7646 } 7647 7648 /** 7649 * @hide 7650 */ 7651 @Override restoreFromExtras(Bundle extras)7652 protected void restoreFromExtras(Bundle extras) { 7653 super.restoreFromExtras(extras); 7654 7655 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 7656 mBigLargeIconSet = true; 7657 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class); 7658 } 7659 7660 if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) { 7661 mPictureContentDescription = 7662 extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION); 7663 } 7664 7665 mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED); 7666 7667 mPictureIcon = getPictureIcon(extras); 7668 } 7669 7670 /** @hide */ 7671 @Nullable getPictureIcon(@ullable Bundle extras)7672 public static Icon getPictureIcon(@Nullable Bundle extras) { 7673 if (extras == null) return null; 7674 // When this style adds a picture, we only add one of the keys. If both were added, 7675 // it would most likely be a legacy app trying to override the picture in some way. 7676 // Because of that case it's better to give precedence to the legacy field. 7677 Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class); 7678 if (bitmapPicture != null) { 7679 return Icon.createWithBitmap(bitmapPicture); 7680 } else { 7681 return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class); 7682 } 7683 } 7684 7685 /** 7686 * @hide 7687 */ 7688 @Override hasSummaryInHeader()7689 public boolean hasSummaryInHeader() { 7690 return false; 7691 } 7692 7693 /** 7694 * @hide 7695 */ 7696 @Override areNotificationsVisiblyDifferent(Style other)7697 public boolean areNotificationsVisiblyDifferent(Style other) { 7698 if (other == null || getClass() != other.getClass()) { 7699 return true; 7700 } 7701 BigPictureStyle otherS = (BigPictureStyle) other; 7702 return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture()); 7703 } 7704 } 7705 7706 /** 7707 * Helper class for generating large-format notifications that include a lot of text. 7708 * 7709 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 7710 * <pre class="prettyprint"> 7711 * Notification notif = new Notification.Builder(mContext) 7712 * .setContentTitle("New mail from " + sender.toString()) 7713 * .setContentText(subject) 7714 * .setSmallIcon(R.drawable.new_mail) 7715 * .setLargeIcon(aBitmap) 7716 * .setStyle(new Notification.BigTextStyle() 7717 * .bigText(aVeryLongString)) 7718 * .build(); 7719 * </pre> 7720 * 7721 * @see Notification#bigContentView 7722 */ 7723 public static class BigTextStyle extends Style { 7724 7725 private CharSequence mBigText; 7726 BigTextStyle()7727 public BigTextStyle() { 7728 } 7729 7730 /** 7731 * @deprecated use {@code BigTextStyle()}. 7732 */ 7733 @Deprecated BigTextStyle(Builder builder)7734 public BigTextStyle(Builder builder) { 7735 setBuilder(builder); 7736 } 7737 7738 /** 7739 * Overrides ContentTitle in the big form of the template. 7740 * This defaults to the value passed to setContentTitle(). 7741 */ setBigContentTitle(CharSequence title)7742 public BigTextStyle setBigContentTitle(CharSequence title) { 7743 internalSetBigContentTitle(safeCharSequence(title)); 7744 return this; 7745 } 7746 7747 /** 7748 * Set the first line of text after the detail section in the big form of the template. 7749 */ setSummaryText(CharSequence cs)7750 public BigTextStyle setSummaryText(CharSequence cs) { 7751 internalSetSummaryText(safeCharSequence(cs)); 7752 return this; 7753 } 7754 7755 /** 7756 * Provide the longer text to be displayed in the big form of the 7757 * template in place of the content text. 7758 */ bigText(CharSequence cs)7759 public BigTextStyle bigText(CharSequence cs) { 7760 mBigText = safeCharSequence(cs); 7761 return this; 7762 } 7763 7764 /** 7765 * @hide 7766 */ getBigText()7767 public CharSequence getBigText() { 7768 return mBigText; 7769 } 7770 7771 /** 7772 * @hide 7773 */ addExtras(Bundle extras)7774 public void addExtras(Bundle extras) { 7775 super.addExtras(extras); 7776 7777 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 7778 } 7779 7780 /** 7781 * @hide 7782 */ 7783 @Override restoreFromExtras(Bundle extras)7784 protected void restoreFromExtras(Bundle extras) { 7785 super.restoreFromExtras(extras); 7786 7787 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 7788 } 7789 7790 /** 7791 * @param increasedHeight true if this layout be created with an increased height. 7792 * 7793 * @hide 7794 */ 7795 @Override makeContentView(boolean increasedHeight)7796 public RemoteViews makeContentView(boolean increasedHeight) { 7797 if (increasedHeight) { 7798 ArrayList<Action> originalActions = mBuilder.mActions; 7799 mBuilder.mActions = new ArrayList<>(); 7800 RemoteViews remoteViews = makeBigContentView(); 7801 mBuilder.mActions = originalActions; 7802 return remoteViews; 7803 } 7804 return super.makeContentView(increasedHeight); 7805 } 7806 7807 /** 7808 * @hide 7809 */ 7810 @Override makeHeadsUpContentView(boolean increasedHeight)7811 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7812 if (increasedHeight && mBuilder.mActions.size() > 0) { 7813 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP? 7814 return makeBigContentView(); 7815 } 7816 return super.makeHeadsUpContentView(increasedHeight); 7817 } 7818 7819 /** 7820 * @hide 7821 */ makeBigContentView()7822 public RemoteViews makeBigContentView() { 7823 StandardTemplateParams p = mBuilder.mParams.reset() 7824 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 7825 .allowTextWithProgress(true) 7826 .textViewId(R.id.big_text) 7827 .fillTextsFrom(mBuilder); 7828 7829 // Replace the text with the big text, but only if the big text is not empty. 7830 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 7831 if (!TextUtils.isEmpty(bigTextText)) { 7832 p.text(bigTextText); 7833 } 7834 7835 return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */); 7836 } 7837 7838 /** 7839 * @hide 7840 * Spans are ignored when comparing text for visual difference. 7841 */ 7842 @Override areNotificationsVisiblyDifferent(Style other)7843 public boolean areNotificationsVisiblyDifferent(Style other) { 7844 if (other == null || getClass() != other.getClass()) { 7845 return true; 7846 } 7847 BigTextStyle newS = (BigTextStyle) other; 7848 return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText())); 7849 } 7850 7851 } 7852 7853 /** 7854 * Helper class for generating large-format notifications that include multiple back-and-forth 7855 * messages of varying types between any number of people. 7856 * 7857 * <p> 7858 * If the platform does not provide large-format notifications, this method has no effect. The 7859 * user will always see the normal notification view. 7860 * 7861 * <p> 7862 * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is 7863 * required to use the {@link Person} class in order to get an optimal rendering of the 7864 * notification and its avatars. For conversations involving multiple people, the app should 7865 * also make sure that it marks the conversation as a group with 7866 * {@link #setGroupConversation(boolean)}. 7867 * 7868 * <p> 7869 * From Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, messaging style 7870 * notifications that are associated with a valid conversation shortcut 7871 * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated 7872 * conversation section in the shade above non-conversation alerting and silence notifications. 7873 * To be a valid conversation shortcut, the shortcut must be a 7874 * {@link ShortcutInfo#setLongLived()} dynamic or cached sharing shortcut. 7875 * 7876 * <p> 7877 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. 7878 * Here's an example of how this may be used: 7879 * <pre class="prettyprint"> 7880 * 7881 * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build(); 7882 * MessagingStyle style = new MessagingStyle(user) 7883 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson()) 7884 * .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson()) 7885 * .setGroupConversation(hasMultiplePeople()); 7886 * 7887 * Notification noti = new Notification.Builder() 7888 * .setContentTitle("2 new messages with " + sender.toString()) 7889 * .setContentText(subject) 7890 * .setSmallIcon(R.drawable.new_message) 7891 * .setLargeIcon(aBitmap) 7892 * .setStyle(style) 7893 * .build(); 7894 * </pre> 7895 */ 7896 public static class MessagingStyle extends Style { 7897 7898 /** 7899 * The maximum number of messages that will be retained in the Notification itself (the 7900 * number displayed is up to the platform). 7901 */ 7902 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 7903 7904 7905 /** @hide */ 7906 public static final int CONVERSATION_TYPE_LEGACY = 0; 7907 /** @hide */ 7908 public static final int CONVERSATION_TYPE_NORMAL = 1; 7909 /** @hide */ 7910 public static final int CONVERSATION_TYPE_IMPORTANT = 2; 7911 7912 /** @hide */ 7913 @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = { 7914 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT 7915 }) 7916 @Retention(RetentionPolicy.SOURCE) 7917 public @interface ConversationType {} 7918 7919 @NonNull Person mUser; 7920 @Nullable CharSequence mConversationTitle; 7921 @Nullable Icon mShortcutIcon; 7922 List<Message> mMessages = new ArrayList<>(); 7923 List<Message> mHistoricMessages = new ArrayList<>(); 7924 boolean mIsGroupConversation; 7925 @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; 7926 int mUnreadMessageCount; 7927 MessagingStyle()7928 MessagingStyle() { 7929 } 7930 7931 /** 7932 * @param userDisplayName Required - the name to be displayed for any replies sent by the 7933 * user before the posting app reposts the notification with those messages after they've 7934 * been actually sent and in previous messages sent by the user added in 7935 * {@link #addMessage(Notification.MessagingStyle.Message)} 7936 * 7937 * @deprecated use {@code MessagingStyle(Person)} 7938 */ MessagingStyle(@onNull CharSequence userDisplayName)7939 public MessagingStyle(@NonNull CharSequence userDisplayName) { 7940 this(new Person.Builder().setName(userDisplayName).build()); 7941 } 7942 7943 /** 7944 * @param user Required - The person displayed for any messages that are sent by the 7945 * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} 7946 * who don't have a Person associated with it will be displayed as if they were sent 7947 * by this user. The user also needs to have a valid name associated with it, which is 7948 * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}. 7949 */ MessagingStyle(@onNull Person user)7950 public MessagingStyle(@NonNull Person user) { 7951 mUser = user; 7952 } 7953 7954 /** 7955 * Validate that this style was properly composed. This is called at build time. 7956 * @hide 7957 */ 7958 @Override validate(Context context)7959 public void validate(Context context) { 7960 super.validate(context); 7961 if (context.getApplicationInfo().targetSdkVersion 7962 >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { 7963 throw new RuntimeException("User must be valid and have a name."); 7964 } 7965 } 7966 7967 /** 7968 * @return the text that should be displayed in the statusBar when heads upped. 7969 * If {@code null} is returned, the default implementation will be used. 7970 * 7971 * @hide 7972 */ 7973 @Override getHeadsUpStatusBarText()7974 public CharSequence getHeadsUpStatusBarText() { 7975 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7976 ? super.mBigContentTitle 7977 : mConversationTitle; 7978 if (mConversationType == CONVERSATION_TYPE_LEGACY 7979 && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { 7980 return conversationTitle; 7981 } 7982 return null; 7983 } 7984 7985 /** 7986 * @return the user to be displayed for any replies sent by the user 7987 */ 7988 @NonNull getUser()7989 public Person getUser() { 7990 return mUser; 7991 } 7992 7993 /** 7994 * Returns the name to be displayed for any replies sent by the user 7995 * 7996 * @deprecated use {@link #getUser()} instead 7997 */ getUserDisplayName()7998 public CharSequence getUserDisplayName() { 7999 return mUser.getName(); 8000 } 8001 8002 /** 8003 * Sets the title to be displayed on this conversation. May be set to {@code null}. 8004 * 8005 * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored 8006 * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. 8007 * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing, 8008 * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title 8009 * instead. 8010 * 8011 * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your 8012 * application's target version is less than {@link Build.VERSION_CODES#P}, setting a 8013 * conversation title to a non-null value will make {@link #isGroupConversation()} return 8014 * {@code true} and passing {@code null} will make it return {@code false}. In 8015 * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} 8016 * to set group conversation status. 8017 * 8018 * @param conversationTitle Title displayed for this conversation 8019 * @return this object for method chaining 8020 */ setConversationTitle(@ullable CharSequence conversationTitle)8021 public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { 8022 mConversationTitle = conversationTitle; 8023 return this; 8024 } 8025 8026 /** 8027 * Return the title to be displayed on this conversation. May return {@code null}. 8028 */ 8029 @Nullable getConversationTitle()8030 public CharSequence getConversationTitle() { 8031 return mConversationTitle; 8032 } 8033 8034 /** 8035 * Sets the icon to be displayed on the conversation, derived from the shortcutId. 8036 * 8037 * @hide 8038 */ setShortcutIcon(@ullable Icon conversationIcon)8039 public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) { 8040 mShortcutIcon = conversationIcon; 8041 return this; 8042 } 8043 8044 /** 8045 * Return the icon to be displayed on this conversation, derived from the shortcutId. May 8046 * return {@code null}. 8047 * 8048 * @hide 8049 */ 8050 @Nullable getShortcutIcon()8051 public Icon getShortcutIcon() { 8052 return mShortcutIcon; 8053 } 8054 8055 /** 8056 * Sets the conversation type of this MessageStyle notification. 8057 * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R, 8058 * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and 8059 * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments. 8060 * 8061 * @hide 8062 */ setConversationType(@onversationType int conversationType)8063 public MessagingStyle setConversationType(@ConversationType int conversationType) { 8064 mConversationType = conversationType; 8065 return this; 8066 } 8067 8068 /** @hide */ 8069 @ConversationType getConversationType()8070 public int getConversationType() { 8071 return mConversationType; 8072 } 8073 8074 /** @hide */ getUnreadMessageCount()8075 public int getUnreadMessageCount() { 8076 return mUnreadMessageCount; 8077 } 8078 8079 /** @hide */ setUnreadMessageCount(int unreadMessageCount)8080 public MessagingStyle setUnreadMessageCount(int unreadMessageCount) { 8081 mUnreadMessageCount = unreadMessageCount; 8082 return this; 8083 } 8084 8085 /** 8086 * Adds a message for display by this notification. Convenience call for a simple 8087 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 8088 * @param text A {@link CharSequence} to be displayed as the message content 8089 * @param timestamp Time in milliseconds at which the message arrived 8090 * @param sender A {@link CharSequence} to be used for displaying the name of the 8091 * sender. Should be <code>null</code> for messages by the current user, in which case 8092 * the platform will insert {@link #getUserDisplayName()}. 8093 * Should be unique amongst all individuals in the conversation, and should be 8094 * consistent during re-posts of the notification. 8095 * 8096 * @see Message#Message(CharSequence, long, CharSequence) 8097 * 8098 * @return this object for method chaining 8099 * 8100 * @deprecated use {@link #addMessage(CharSequence, long, Person)} 8101 */ addMessage(CharSequence text, long timestamp, CharSequence sender)8102 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 8103 return addMessage(text, timestamp, 8104 sender == null ? null : new Person.Builder().setName(sender).build()); 8105 } 8106 8107 /** 8108 * Adds a message for display by this notification. Convenience call for a simple 8109 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 8110 * @param text A {@link CharSequence} to be displayed as the message content 8111 * @param timestamp Time in milliseconds at which the message arrived 8112 * @param sender The {@link Person} who sent the message. 8113 * Should be <code>null</code> for messages by the current user, in which case 8114 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8115 * 8116 * @see Message#Message(CharSequence, long, CharSequence) 8117 * 8118 * @return this object for method chaining 8119 */ addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)8120 public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, 8121 @Nullable Person sender) { 8122 return addMessage(new Message(text, timestamp, sender)); 8123 } 8124 8125 /** 8126 * Adds a {@link Message} for display in this notification. 8127 * 8128 * <p>The messages should be added in chronologic order, i.e. the oldest first, 8129 * the newest last. 8130 * 8131 * @param message The {@link Message} to be displayed 8132 * @return this object for method chaining 8133 */ addMessage(Message message)8134 public MessagingStyle addMessage(Message message) { 8135 mMessages.add(message); 8136 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 8137 mMessages.remove(0); 8138 } 8139 return this; 8140 } 8141 8142 /** 8143 * Adds a {@link Message} for historic context in this notification. 8144 * 8145 * <p>Messages should be added as historic if they are not the main subject of the 8146 * notification but may give context to a conversation. The system may choose to present 8147 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}. 8148 * 8149 * <p>The messages should be added in chronologic order, i.e. the oldest first, 8150 * the newest last. 8151 * 8152 * @param message The historic {@link Message} to be added 8153 * @return this object for method chaining 8154 */ addHistoricMessage(Message message)8155 public MessagingStyle addHistoricMessage(Message message) { 8156 mHistoricMessages.add(message); 8157 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 8158 mHistoricMessages.remove(0); 8159 } 8160 return this; 8161 } 8162 8163 /** 8164 * Gets the list of {@code Message} objects that represent the notification. 8165 */ getMessages()8166 public List<Message> getMessages() { 8167 return mMessages; 8168 } 8169 8170 /** 8171 * Gets the list of historic {@code Message}s in the notification. 8172 */ getHistoricMessages()8173 public List<Message> getHistoricMessages() { 8174 return mHistoricMessages; 8175 } 8176 8177 /** 8178 * Sets whether this conversation notification represents a group. If the app is targeting 8179 * Android P, this is required if the app wants to display the largeIcon set with 8180 * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden. 8181 * 8182 * @param isGroupConversation {@code true} if the conversation represents a group, 8183 * {@code false} otherwise. 8184 * @return this object for method chaining 8185 */ setGroupConversation(boolean isGroupConversation)8186 public MessagingStyle setGroupConversation(boolean isGroupConversation) { 8187 mIsGroupConversation = isGroupConversation; 8188 return this; 8189 } 8190 8191 /** 8192 * Returns {@code true} if this notification represents a group conversation, otherwise 8193 * {@code false}. 8194 * 8195 * <p> If the application that generated this {@link MessagingStyle} targets an SDK version 8196 * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or 8197 * not the conversation title is set; returning {@code true} if the conversation title is 8198 * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, 8199 * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for 8200 * named, non-group conversations. 8201 * 8202 * @see #setConversationTitle(CharSequence) 8203 */ isGroupConversation()8204 public boolean isGroupConversation() { 8205 // When target SDK version is < P, a non-null conversation title dictates if this is 8206 // as group conversation. 8207 if (mBuilder != null 8208 && mBuilder.mContext.getApplicationInfo().targetSdkVersion 8209 < Build.VERSION_CODES.P) { 8210 return mConversationTitle != null; 8211 } 8212 8213 return mIsGroupConversation; 8214 } 8215 8216 /** 8217 * @hide 8218 */ 8219 @Override addExtras(Bundle extras)8220 public void addExtras(Bundle extras) { 8221 super.addExtras(extras); 8222 addExtras(extras, false, 0); 8223 } 8224 8225 /** 8226 * @hide 8227 */ addExtras(Bundle extras, boolean ensureContrast, int backgroundColor)8228 public void addExtras(Bundle extras, boolean ensureContrast, int backgroundColor) { 8229 if (mUser != null) { 8230 // For legacy usages 8231 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); 8232 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); 8233 } 8234 if (mConversationTitle != null) { 8235 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 8236 } 8237 if (!mMessages.isEmpty()) { 8238 extras.putParcelableArray(EXTRA_MESSAGES, 8239 getBundleArrayForMessages(mMessages, ensureContrast, backgroundColor)); 8240 } 8241 if (!mHistoricMessages.isEmpty()) { 8242 extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, getBundleArrayForMessages( 8243 mHistoricMessages, ensureContrast, backgroundColor)); 8244 } 8245 if (mShortcutIcon != null) { 8246 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon); 8247 } 8248 extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount); 8249 8250 fixTitleAndTextExtras(extras); 8251 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); 8252 } 8253 getBundleArrayForMessages(List<Message> messages, boolean ensureContrast, int backgroundColor)8254 private static Bundle[] getBundleArrayForMessages(List<Message> messages, 8255 boolean ensureContrast, int backgroundColor) { 8256 Bundle[] bundles = new Bundle[messages.size()]; 8257 final int N = messages.size(); 8258 for (int i = 0; i < N; i++) { 8259 final Message m = messages.get(i); 8260 if (ensureContrast) { 8261 m.ensureColorContrast(backgroundColor); 8262 } 8263 bundles[i] = m.toBundle(); 8264 } 8265 return bundles; 8266 } 8267 fixTitleAndTextExtras(Bundle extras)8268 private void fixTitleAndTextExtras(Bundle extras) { 8269 Message m = findLatestIncomingMessage(); 8270 CharSequence text = (m == null) ? null : m.mText; 8271 CharSequence sender = m == null ? null 8272 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 8273 ? mUser.getName() : m.mSender.getName(); 8274 CharSequence title; 8275 if (!TextUtils.isEmpty(mConversationTitle)) { 8276 if (!TextUtils.isEmpty(sender)) { 8277 BidiFormatter bidi = BidiFormatter.getInstance(); 8278 title = mBuilder.mContext.getString( 8279 com.android.internal.R.string.notification_messaging_title_template, 8280 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); 8281 } else { 8282 title = mConversationTitle; 8283 } 8284 } else { 8285 title = sender; 8286 } 8287 8288 if (title != null) { 8289 extras.putCharSequence(EXTRA_TITLE, title); 8290 } 8291 if (text != null) { 8292 extras.putCharSequence(EXTRA_TEXT, text); 8293 } 8294 } 8295 8296 /** 8297 * @hide 8298 */ 8299 @Override restoreFromExtras(Bundle extras)8300 protected void restoreFromExtras(Bundle extras) { 8301 super.restoreFromExtras(extras); 8302 8303 Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); 8304 if (user == null) { 8305 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 8306 mUser = new Person.Builder().setName(displayName).build(); 8307 } else { 8308 mUser = user; 8309 } 8310 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 8311 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class); 8312 mMessages = Message.getMessagesFromBundleArray(messages); 8313 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, 8314 Parcelable.class); 8315 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); 8316 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); 8317 mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 8318 mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class); 8319 } 8320 8321 /** 8322 * @hide 8323 */ 8324 @Override makeContentView(boolean increasedHeight)8325 public RemoteViews makeContentView(boolean increasedHeight) { 8326 // All messaging templates contain the actions 8327 ArrayList<Action> originalActions = mBuilder.mActions; 8328 try { 8329 mBuilder.mActions = new ArrayList<>(); 8330 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL); 8331 } finally { 8332 mBuilder.mActions = originalActions; 8333 } 8334 } 8335 8336 /** 8337 * @hide 8338 * Spans are ignored when comparing text for visual difference. 8339 */ 8340 @Override areNotificationsVisiblyDifferent(Style other)8341 public boolean areNotificationsVisiblyDifferent(Style other) { 8342 if (other == null || getClass() != other.getClass()) { 8343 return true; 8344 } 8345 MessagingStyle newS = (MessagingStyle) other; 8346 List<MessagingStyle.Message> oldMs = getMessages(); 8347 List<MessagingStyle.Message> newMs = newS.getMessages(); 8348 8349 if (oldMs == null || newMs == null) { 8350 newMs = new ArrayList<>(); 8351 } 8352 8353 int n = oldMs.size(); 8354 if (n != newMs.size()) { 8355 return true; 8356 } 8357 for (int i = 0; i < n; i++) { 8358 MessagingStyle.Message oldM = oldMs.get(i); 8359 MessagingStyle.Message newM = newMs.get(i); 8360 if (!Objects.equals( 8361 String.valueOf(oldM.getText()), 8362 String.valueOf(newM.getText()))) { 8363 return true; 8364 } 8365 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { 8366 return true; 8367 } 8368 String oldSender = String.valueOf(oldM.getSenderPerson() == null 8369 ? oldM.getSender() 8370 : oldM.getSenderPerson().getName()); 8371 String newSender = String.valueOf(newM.getSenderPerson() == null 8372 ? newM.getSender() 8373 : newM.getSenderPerson().getName()); 8374 if (!Objects.equals(oldSender, newSender)) { 8375 return true; 8376 } 8377 8378 String oldKey = oldM.getSenderPerson() == null 8379 ? null : oldM.getSenderPerson().getKey(); 8380 String newKey = newM.getSenderPerson() == null 8381 ? null : newM.getSenderPerson().getKey(); 8382 if (!Objects.equals(oldKey, newKey)) { 8383 return true; 8384 } 8385 // Other fields (like timestamp) intentionally excluded 8386 } 8387 return false; 8388 } 8389 findLatestIncomingMessage()8390 private Message findLatestIncomingMessage() { 8391 return findLatestIncomingMessage(mMessages); 8392 } 8393 8394 /** 8395 * @hide 8396 */ 8397 @Nullable findLatestIncomingMessage( List<Message> messages)8398 public static Message findLatestIncomingMessage( 8399 List<Message> messages) { 8400 for (int i = messages.size() - 1; i >= 0; i--) { 8401 Message m = messages.get(i); 8402 // Incoming messages have a non-empty sender. 8403 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { 8404 return m; 8405 } 8406 } 8407 if (!messages.isEmpty()) { 8408 // No incoming messages, fall back to outgoing message 8409 return messages.get(messages.size() - 1); 8410 } 8411 return null; 8412 } 8413 8414 /** 8415 * @hide 8416 */ 8417 @Override makeBigContentView()8418 public RemoteViews makeBigContentView() { 8419 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG); 8420 } 8421 8422 /** 8423 * Create a messaging layout. 8424 * 8425 * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG, 8426 * VIEW_TYPE_HEADS_UP 8427 * @return the created remoteView. 8428 */ 8429 @NonNull makeMessagingView(int viewType)8430 private RemoteViews makeMessagingView(int viewType) { 8431 boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG; 8432 boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL; 8433 boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 8434 boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; 8435 boolean isHeaderless = !isConversationLayout && isCollapsed; 8436 8437 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 8438 ? super.mBigContentTitle 8439 : mConversationTitle; 8440 boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion 8441 >= Build.VERSION_CODES.P; 8442 boolean isOneToOne; 8443 CharSequence nameReplacement = null; 8444 if (!atLeastP) { 8445 isOneToOne = TextUtils.isEmpty(conversationTitle); 8446 if (hasOnlyWhiteSpaceSenders()) { 8447 isOneToOne = true; 8448 nameReplacement = conversationTitle; 8449 conversationTitle = null; 8450 } 8451 } else { 8452 isOneToOne = !isGroupConversation(); 8453 } 8454 if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) { 8455 conversationTitle = getOtherPersonName(); 8456 } 8457 8458 Icon largeIcon = mBuilder.mN.mLargeIcon; 8459 TemplateBindResult bindResult = new TemplateBindResult(); 8460 StandardTemplateParams p = mBuilder.mParams.reset() 8461 .viewType(viewType) 8462 .highlightExpander(isConversationLayout) 8463 .hideProgress(true) 8464 .title(isHeaderless ? conversationTitle : null) 8465 .text(null) 8466 .hideLeftIcon(isOneToOne) 8467 .hideRightIcon(hideRightIcons || isOneToOne) 8468 .headerTextSecondary(isHeaderless ? null : conversationTitle); 8469 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 8470 isConversationLayout 8471 ? mBuilder.getConversationLayoutResource() 8472 : isCollapsed 8473 ? mBuilder.getMessagingLayoutResource() 8474 : mBuilder.getBigMessagingLayoutResource(), 8475 p, 8476 bindResult); 8477 if (isConversationLayout) { 8478 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p); 8479 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 8480 } 8481 8482 addExtras(mBuilder.mN.extras, true, mBuilder.getBackgroundColor(p)); 8483 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 8484 mBuilder.getSmallIconColor(p)); 8485 contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", 8486 mBuilder.getPrimaryTextColor(p)); 8487 contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", 8488 mBuilder.getSecondaryTextColor(p)); 8489 contentView.setInt(R.id.status_bar_latest_event_content, 8490 "setNotificationBackgroundColor", 8491 mBuilder.getBackgroundColor(p)); 8492 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", 8493 isCollapsed); 8494 contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", 8495 mBuilder.mN.mLargeIcon); 8496 contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", 8497 nameReplacement); 8498 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", 8499 isOneToOne); 8500 contentView.setCharSequence(R.id.status_bar_latest_event_content, 8501 "setConversationTitle", conversationTitle); 8502 if (isConversationLayout) { 8503 contentView.setIcon(R.id.status_bar_latest_event_content, 8504 "setShortcutIcon", mShortcutIcon); 8505 contentView.setBoolean(R.id.status_bar_latest_event_content, 8506 "setIsImportantConversation", isImportantConversation); 8507 } 8508 if (isHeaderless) { 8509 // Collapsed legacy messaging style has a 1-line limit. 8510 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); 8511 } 8512 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 8513 largeIcon); 8514 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 8515 mBuilder.mN.extras); 8516 return contentView; 8517 } 8518 getKey(Person person)8519 private CharSequence getKey(Person person) { 8520 return person == null ? null 8521 : person.getKey() == null ? person.getName() : person.getKey(); 8522 } 8523 getOtherPersonName()8524 private CharSequence getOtherPersonName() { 8525 CharSequence userKey = getKey(mUser); 8526 for (int i = mMessages.size() - 1; i >= 0; i--) { 8527 Person sender = mMessages.get(i).getSenderPerson(); 8528 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) { 8529 return sender.getName(); 8530 } 8531 } 8532 return null; 8533 } 8534 hasOnlyWhiteSpaceSenders()8535 private boolean hasOnlyWhiteSpaceSenders() { 8536 for (int i = 0; i < mMessages.size(); i++) { 8537 Message m = mMessages.get(i); 8538 Person sender = m.getSenderPerson(); 8539 if (sender != null && !isWhiteSpace(sender.getName())) { 8540 return false; 8541 } 8542 } 8543 return true; 8544 } 8545 isWhiteSpace(CharSequence sender)8546 private boolean isWhiteSpace(CharSequence sender) { 8547 if (TextUtils.isEmpty(sender)) { 8548 return true; 8549 } 8550 if (sender.toString().matches("^\\s*$")) { 8551 return true; 8552 } 8553 // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround 8554 // For the presentation that we had. 8555 for (int i = 0; i < sender.length(); i++) { 8556 char c = sender.charAt(i); 8557 if (c != '\u200B') { 8558 return false; 8559 } 8560 } 8561 return true; 8562 } 8563 8564 /** 8565 * @hide 8566 */ 8567 @Override makeHeadsUpContentView(boolean increasedHeight)8568 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8569 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 8570 } 8571 8572 /** 8573 * @hide 8574 */ 8575 @Override reduceImageSizes(Context context)8576 public void reduceImageSizes(Context context) { 8577 super.reduceImageSizes(context); 8578 Resources resources = context.getResources(); 8579 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 8580 if (mShortcutIcon != null) { 8581 int maxSize = resources.getDimensionPixelSize( 8582 isLowRam ? R.dimen.notification_small_icon_size_low_ram 8583 : R.dimen.notification_small_icon_size); 8584 mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize); 8585 } 8586 8587 int maxAvatarSize = resources.getDimensionPixelSize( 8588 isLowRam ? R.dimen.notification_person_icon_max_size_low_ram 8589 : R.dimen.notification_person_icon_max_size); 8590 if (mUser != null && mUser.getIcon() != null) { 8591 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize); 8592 } 8593 8594 reduceMessagesIconSizes(mMessages, maxAvatarSize); 8595 reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize); 8596 } 8597 8598 /** 8599 * @hide 8600 */ reduceMessagesIconSizes(@ullable List<Message> messages, int maxSize)8601 private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) { 8602 if (messages == null) { 8603 return; 8604 } 8605 8606 for (Message message : messages) { 8607 Person sender = message.mSender; 8608 if (sender != null) { 8609 Icon icon = sender.getIcon(); 8610 if (icon != null) { 8611 icon.scaleDownIfNecessary(maxSize, maxSize); 8612 } 8613 } 8614 } 8615 } 8616 8617 public static final class Message { 8618 /** @hide */ 8619 public static final String KEY_TEXT = "text"; 8620 static final String KEY_TIMESTAMP = "time"; 8621 static final String KEY_SENDER = "sender"; 8622 static final String KEY_SENDER_PERSON = "sender_person"; 8623 static final String KEY_DATA_MIME_TYPE = "type"; 8624 static final String KEY_DATA_URI= "uri"; 8625 static final String KEY_EXTRAS_BUNDLE = "extras"; 8626 static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history"; 8627 8628 private CharSequence mText; 8629 private final long mTimestamp; 8630 @Nullable 8631 private final Person mSender; 8632 /** True if this message was generated from the extra 8633 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} 8634 */ 8635 private final boolean mRemoteInputHistory; 8636 8637 private Bundle mExtras = new Bundle(); 8638 private String mDataMimeType; 8639 private Uri mDataUri; 8640 8641 /** 8642 * Constructor 8643 * @param text A {@link CharSequence} to be displayed as the message content 8644 * @param timestamp Time at which the message arrived 8645 * @param sender A {@link CharSequence} to be used for displaying the name of the 8646 * sender. Should be <code>null</code> for messages by the current user, in which case 8647 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 8648 * Should be unique amongst all individuals in the conversation, and should be 8649 * consistent during re-posts of the notification. 8650 * 8651 * @deprecated use {@code Message(CharSequence, long, Person)} 8652 */ Message(CharSequence text, long timestamp, CharSequence sender)8653 public Message(CharSequence text, long timestamp, CharSequence sender){ 8654 this(text, timestamp, sender == null ? null 8655 : new Person.Builder().setName(sender).build()); 8656 } 8657 8658 /** 8659 * Constructor 8660 * @param text A {@link CharSequence} to be displayed as the message content 8661 * @param timestamp Time at which the message arrived 8662 * @param sender The {@link Person} who sent the message. 8663 * Should be <code>null</code> for messages by the current user, in which case 8664 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8665 * <p> 8666 * The person provided should contain an Icon, set with 8667 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8668 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8669 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8670 * to differentiate between the different users. 8671 * </p> 8672 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8673 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { 8674 this(text, timestamp, sender, false /* remoteHistory */); 8675 } 8676 8677 /** 8678 * Constructor 8679 * @param text A {@link CharSequence} to be displayed as the message content 8680 * @param timestamp Time at which the message arrived 8681 * @param sender The {@link Person} who sent the message. 8682 * Should be <code>null</code> for messages by the current user, in which case 8683 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8684 * @param remoteInputHistory True if the messages was generated from the extra 8685 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8686 * <p> 8687 * The person provided should contain an Icon, set with 8688 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8689 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8690 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8691 * to differentiate between the different users. 8692 * </p> 8693 * @hide 8694 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8695 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender, 8696 boolean remoteInputHistory) { 8697 mText = safeCharSequence(text); 8698 mTimestamp = timestamp; 8699 mSender = sender; 8700 mRemoteInputHistory = remoteInputHistory; 8701 } 8702 8703 /** 8704 * Sets a binary blob of data and an associated MIME type for a message. In the case 8705 * where the platform doesn't support the MIME type, the original text provided in the 8706 * constructor will be used. 8707 * @param dataMimeType The MIME type of the content. See 8708 * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of 8709 * supported image MIME types. 8710 * @param dataUri The uri containing the content whose type is given by the MIME type. 8711 * <p class="note"> 8712 * Notification Listeners including the System UI need permission to access the 8713 * data the Uri points to. The recommended ways to do this are: 8714 * <ol> 8715 * <li>Store the data in your own ContentProvider, making sure that other apps have 8716 * the correct permission to access your provider. The preferred mechanism for 8717 * providing access is to use per-URI permissions which are temporary and only 8718 * grant access to the receiving application. An easy way to create a 8719 * ContentProvider like this is to use the FileProvider helper class.</li> 8720 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 8721 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 8722 * also store non-media types (see MediaStore.Files for more info). Files can be 8723 * inserted into the MediaStore using scanFile() after which a content:// style 8724 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 8725 * Note that once added to the system MediaStore the content is accessible to any 8726 * app on the device.</li> 8727 * </ol> 8728 * @return this object for method chaining 8729 */ setData(String dataMimeType, Uri dataUri)8730 public Message setData(String dataMimeType, Uri dataUri) { 8731 mDataMimeType = dataMimeType; 8732 mDataUri = dataUri; 8733 return this; 8734 } 8735 8736 /** 8737 * Updates TextAppearance spans in the message text so it has sufficient contrast 8738 * against its background. 8739 * @hide 8740 */ ensureColorContrast(int backgroundColor)8741 public void ensureColorContrast(int backgroundColor) { 8742 mText = ContrastColorUtil.ensureColorSpanContrast(mText, backgroundColor); 8743 } 8744 8745 /** 8746 * Get the text to be used for this message, or the fallback text if a type and content 8747 * Uri have been set 8748 */ getText()8749 public CharSequence getText() { 8750 return mText; 8751 } 8752 8753 /** 8754 * Get the time at which this message arrived 8755 */ getTimestamp()8756 public long getTimestamp() { 8757 return mTimestamp; 8758 } 8759 8760 /** 8761 * Get the extras Bundle for this message. 8762 */ getExtras()8763 public Bundle getExtras() { 8764 return mExtras; 8765 } 8766 8767 /** 8768 * Get the text used to display the contact's name in the messaging experience 8769 * 8770 * @deprecated use {@link #getSenderPerson()} 8771 */ getSender()8772 public CharSequence getSender() { 8773 return mSender == null ? null : mSender.getName(); 8774 } 8775 8776 /** 8777 * Get the sender associated with this message. 8778 */ 8779 @Nullable getSenderPerson()8780 public Person getSenderPerson() { 8781 return mSender; 8782 } 8783 8784 /** 8785 * Get the MIME type of the data pointed to by the Uri 8786 */ getDataMimeType()8787 public String getDataMimeType() { 8788 return mDataMimeType; 8789 } 8790 8791 /** 8792 * Get the Uri pointing to the content of the message. Can be null, in which case 8793 * {@see #getText()} is used. 8794 */ getDataUri()8795 public Uri getDataUri() { 8796 return mDataUri; 8797 } 8798 8799 /** 8800 * @return True if the message was generated from 8801 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8802 * @hide 8803 */ isRemoteInputHistory()8804 public boolean isRemoteInputHistory() { 8805 return mRemoteInputHistory; 8806 } 8807 8808 /** 8809 * @hide 8810 */ 8811 @VisibleForTesting toBundle()8812 public Bundle toBundle() { 8813 Bundle bundle = new Bundle(); 8814 if (mText != null) { 8815 bundle.putCharSequence(KEY_TEXT, mText); 8816 } 8817 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 8818 if (mSender != null) { 8819 // Legacy listeners need this 8820 bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName())); 8821 bundle.putParcelable(KEY_SENDER_PERSON, mSender); 8822 } 8823 if (mDataMimeType != null) { 8824 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 8825 } 8826 if (mDataUri != null) { 8827 bundle.putParcelable(KEY_DATA_URI, mDataUri); 8828 } 8829 if (mExtras != null) { 8830 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 8831 } 8832 if (mRemoteInputHistory) { 8833 bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory); 8834 } 8835 return bundle; 8836 } 8837 8838 /** 8839 * See {@link Notification#visitUris(Consumer)}. 8840 * 8841 * @hide 8842 */ visitUris(@onNull Consumer<Uri> visitor)8843 public void visitUris(@NonNull Consumer<Uri> visitor) { 8844 visitor.accept(getDataUri()); 8845 if (mSender != null) { 8846 mSender.visitUris(visitor); 8847 } 8848 } 8849 8850 /** 8851 * Returns a list of messages read from the given bundle list, e.g. 8852 * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}. 8853 */ 8854 @NonNull getMessagesFromBundleArray(@ullable Parcelable[] bundles)8855 public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) { 8856 if (bundles == null) { 8857 return new ArrayList<>(); 8858 } 8859 List<Message> messages = new ArrayList<>(bundles.length); 8860 for (int i = 0; i < bundles.length; i++) { 8861 if (bundles[i] instanceof Bundle) { 8862 Message message = getMessageFromBundle((Bundle)bundles[i]); 8863 if (message != null) { 8864 messages.add(message); 8865 } 8866 } 8867 } 8868 return messages; 8869 } 8870 8871 /** 8872 * Returns the message that is stored in the bundle (e.g. one of the values in the lists 8873 * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the 8874 * message couldn't be resolved. 8875 * @hide 8876 */ 8877 @Nullable getMessageFromBundle(@onNull Bundle bundle)8878 public static Message getMessageFromBundle(@NonNull Bundle bundle) { 8879 try { 8880 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 8881 return null; 8882 } else { 8883 8884 Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class); 8885 if (senderPerson == null) { 8886 // Legacy apps that use compat don't actually provide the sender objects 8887 // We need to fix the compat version to provide people / use 8888 // the native api instead 8889 CharSequence senderName = bundle.getCharSequence(KEY_SENDER); 8890 if (senderName != null) { 8891 senderPerson = new Person.Builder().setName(senderName).build(); 8892 } 8893 } 8894 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 8895 bundle.getLong(KEY_TIMESTAMP), 8896 senderPerson, 8897 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false)); 8898 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 8899 bundle.containsKey(KEY_DATA_URI)) { 8900 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 8901 bundle.getParcelable(KEY_DATA_URI, Uri.class)); 8902 } 8903 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 8904 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 8905 } 8906 return message; 8907 } 8908 } catch (ClassCastException e) { 8909 return null; 8910 } 8911 } 8912 } 8913 } 8914 8915 /** 8916 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 8917 * 8918 * Here's how you'd set the <code>InboxStyle</code> on a notification: 8919 * <pre class="prettyprint"> 8920 * Notification notif = new Notification.Builder(mContext) 8921 * .setContentTitle("5 New mails from " + sender.toString()) 8922 * .setContentText(subject) 8923 * .setSmallIcon(R.drawable.new_mail) 8924 * .setLargeIcon(aBitmap) 8925 * .setStyle(new Notification.InboxStyle() 8926 * .addLine(str1) 8927 * .addLine(str2) 8928 * .setContentTitle("") 8929 * .setSummaryText("+3 more")) 8930 * .build(); 8931 * </pre> 8932 * 8933 * @see Notification#bigContentView 8934 */ 8935 public static class InboxStyle extends Style { 8936 8937 /** 8938 * The number of lines of remote input history allowed until we start reducing lines. 8939 */ 8940 private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1; 8941 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 8942 InboxStyle()8943 public InboxStyle() { 8944 } 8945 8946 /** 8947 * @deprecated use {@code InboxStyle()}. 8948 */ 8949 @Deprecated InboxStyle(Builder builder)8950 public InboxStyle(Builder builder) { 8951 setBuilder(builder); 8952 } 8953 8954 /** 8955 * Overrides ContentTitle in the big form of the template. 8956 * This defaults to the value passed to setContentTitle(). 8957 */ setBigContentTitle(CharSequence title)8958 public InboxStyle setBigContentTitle(CharSequence title) { 8959 internalSetBigContentTitle(safeCharSequence(title)); 8960 return this; 8961 } 8962 8963 /** 8964 * Set the first line of text after the detail section in the big form of the template. 8965 */ setSummaryText(CharSequence cs)8966 public InboxStyle setSummaryText(CharSequence cs) { 8967 internalSetSummaryText(safeCharSequence(cs)); 8968 return this; 8969 } 8970 8971 /** 8972 * Append a line to the digest section of the Inbox notification. 8973 */ addLine(CharSequence cs)8974 public InboxStyle addLine(CharSequence cs) { 8975 mTexts.add(safeCharSequence(cs)); 8976 return this; 8977 } 8978 8979 /** 8980 * @hide 8981 */ getLines()8982 public ArrayList<CharSequence> getLines() { 8983 return mTexts; 8984 } 8985 8986 /** 8987 * @hide 8988 */ addExtras(Bundle extras)8989 public void addExtras(Bundle extras) { 8990 super.addExtras(extras); 8991 8992 CharSequence[] a = new CharSequence[mTexts.size()]; 8993 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 8994 } 8995 8996 /** 8997 * @hide 8998 */ 8999 @Override restoreFromExtras(Bundle extras)9000 protected void restoreFromExtras(Bundle extras) { 9001 super.restoreFromExtras(extras); 9002 9003 mTexts.clear(); 9004 if (extras.containsKey(EXTRA_TEXT_LINES)) { 9005 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 9006 } 9007 } 9008 9009 /** 9010 * @hide 9011 */ makeBigContentView()9012 public RemoteViews makeBigContentView() { 9013 StandardTemplateParams p = mBuilder.mParams.reset() 9014 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9015 .fillTextsFrom(mBuilder).text(null); 9016 TemplateBindResult result = new TemplateBindResult(); 9017 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result); 9018 9019 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 9020 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 9021 9022 // Make sure all rows are gone in case we reuse a view. 9023 for (int rowId : rowIds) { 9024 contentView.setViewVisibility(rowId, View.GONE); 9025 } 9026 9027 int i=0; 9028 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 9029 R.dimen.notification_inbox_item_top_padding); 9030 boolean first = true; 9031 int onlyViewId = 0; 9032 int maxRows = rowIds.length; 9033 if (mBuilder.mActions.size() > 0) { 9034 maxRows--; 9035 } 9036 RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( 9037 mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 9038 RemoteInputHistoryItem.class); 9039 if (remoteInputHistory != null 9040 && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { 9041 // Let's remove some messages to make room for the remote input history. 9042 // 1 is always able to fit, but let's remove them if they are 2 or 3 9043 int numRemoteInputs = Math.min(remoteInputHistory.length, 9044 MAX_REMOTE_INPUT_HISTORY_LINES); 9045 int totalNumRows = mTexts.size() + numRemoteInputs 9046 - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION; 9047 if (totalNumRows > maxRows) { 9048 int overflow = totalNumRows - maxRows; 9049 if (mTexts.size() > maxRows) { 9050 // Heuristic: if the Texts don't fit anyway, we'll rather drop the last 9051 // few messages, even with the remote input 9052 maxRows -= overflow; 9053 } else { 9054 // otherwise we drop the first messages 9055 i = overflow; 9056 } 9057 } 9058 } 9059 while (i < mTexts.size() && i < maxRows) { 9060 CharSequence str = mTexts.get(i); 9061 if (!TextUtils.isEmpty(str)) { 9062 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 9063 contentView.setTextViewText(rowIds[i], 9064 mBuilder.ensureColorSpanContrast(mBuilder.processLegacyText(str), p)); 9065 mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); 9066 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 9067 if (first) { 9068 onlyViewId = rowIds[i]; 9069 } else { 9070 onlyViewId = 0; 9071 } 9072 first = false; 9073 } 9074 i++; 9075 } 9076 if (onlyViewId != 0) { 9077 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 9078 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 9079 R.dimen.notification_text_margin_top); 9080 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 9081 } 9082 9083 return contentView; 9084 } 9085 9086 /** 9087 * @hide 9088 */ 9089 @Override areNotificationsVisiblyDifferent(Style other)9090 public boolean areNotificationsVisiblyDifferent(Style other) { 9091 if (other == null || getClass() != other.getClass()) { 9092 return true; 9093 } 9094 InboxStyle newS = (InboxStyle) other; 9095 9096 final ArrayList<CharSequence> myLines = getLines(); 9097 final ArrayList<CharSequence> newLines = newS.getLines(); 9098 final int n = myLines.size(); 9099 if (n != newLines.size()) { 9100 return true; 9101 } 9102 9103 for (int i = 0; i < n; i++) { 9104 if (!Objects.equals( 9105 String.valueOf(myLines.get(i)), 9106 String.valueOf(newLines.get(i)))) { 9107 return true; 9108 } 9109 } 9110 return false; 9111 } 9112 } 9113 9114 /** 9115 * Notification style for media playback notifications. 9116 * 9117 * In the expanded form, {@link Notification#bigContentView}, up to 5 9118 * {@link Notification.Action}s specified with 9119 * {@link Notification.Builder#addAction(Action) addAction} will be 9120 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 9121 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 9122 * treated as album artwork. 9123 * <p> 9124 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 9125 * {@link Notification#contentView}; by providing action indices to 9126 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 9127 * in the standard view alongside the usual content. 9128 * <p> 9129 * Notifications created with MediaStyle will have their category set to 9130 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 9131 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 9132 * <p> 9133 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 9134 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 9135 * the System UI can identify this as a notification representing an active media session 9136 * and respond accordingly (by showing album artwork in the lockscreen, for example). 9137 * 9138 * <p> 9139 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a 9140 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized. 9141 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}. 9142 * <p> 9143 * 9144 * To use this style with your Notification, feed it to 9145 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9146 * <pre class="prettyprint"> 9147 * Notification noti = new Notification.Builder() 9148 * .setSmallIcon(R.drawable.ic_stat_player) 9149 * .setContentTitle("Track title") 9150 * .setContentText("Artist - Album") 9151 * .setLargeIcon(albumArtBitmap)) 9152 * .setStyle(<b>new Notification.MediaStyle()</b> 9153 * .setMediaSession(mySession)) 9154 * .build(); 9155 * </pre> 9156 * 9157 * @see Notification#bigContentView 9158 * @see Notification.Builder#setColorized(boolean) 9159 */ 9160 public static class MediaStyle extends Style { 9161 // Changing max media buttons requires also changing templates 9162 // (notification_template_material_media and notification_template_material_big_media). 9163 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 9164 static final int MAX_MEDIA_BUTTONS = 5; 9165 @IdRes private static final int[] MEDIA_BUTTON_IDS = { 9166 R.id.action0, 9167 R.id.action1, 9168 R.id.action2, 9169 R.id.action3, 9170 R.id.action4, 9171 }; 9172 9173 private int[] mActionsToShowInCompact = null; 9174 private MediaSession.Token mToken; 9175 private CharSequence mDeviceName; 9176 private int mDeviceIcon; 9177 private PendingIntent mDeviceIntent; 9178 MediaStyle()9179 public MediaStyle() { 9180 } 9181 9182 /** 9183 * @deprecated use {@code MediaStyle()}. 9184 */ 9185 @Deprecated MediaStyle(Builder builder)9186 public MediaStyle(Builder builder) { 9187 setBuilder(builder); 9188 } 9189 9190 /** 9191 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 9192 * notification view. 9193 * 9194 * @param actions the indices of the actions to show in the compact notification view 9195 */ setShowActionsInCompactView(int...actions)9196 public MediaStyle setShowActionsInCompactView(int...actions) { 9197 mActionsToShowInCompact = actions; 9198 return this; 9199 } 9200 9201 /** 9202 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 9203 * to provide additional playback information and control to the SystemUI. 9204 */ setMediaSession(MediaSession.Token token)9205 public MediaStyle setMediaSession(MediaSession.Token token) { 9206 mToken = token; 9207 return this; 9208 } 9209 9210 /** 9211 * For media notifications associated with playback on a remote device, provide device 9212 * information that will replace the default values for the output switcher chip on the 9213 * media control, as well as an intent to use when the output switcher chip is tapped, 9214 * on devices where this is supported. 9215 * <p> 9216 * This method is intended for system applications to provide information and/or 9217 * functionality that would otherwise be unavailable to the default output switcher because 9218 * the media originated on a remote device. 9219 * 9220 * @param deviceName The name of the remote device to display 9221 * @param iconResource Icon resource representing the device 9222 * @param chipIntent PendingIntent to send when the output switcher is tapped. May be 9223 * {@code null}, in which case the output switcher will be disabled. 9224 * This intent should open an Activity or it will be ignored. 9225 * @return MediaStyle 9226 */ 9227 @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) 9228 @NonNull setRemotePlaybackInfo(@onNull CharSequence deviceName, @DrawableRes int iconResource, @Nullable PendingIntent chipIntent)9229 public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName, 9230 @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) { 9231 mDeviceName = deviceName; 9232 mDeviceIcon = iconResource; 9233 mDeviceIntent = chipIntent; 9234 return this; 9235 } 9236 9237 /** 9238 * @hide 9239 */ 9240 @Override 9241 @UnsupportedAppUsage buildStyled(Notification wip)9242 public Notification buildStyled(Notification wip) { 9243 super.buildStyled(wip); 9244 if (wip.category == null) { 9245 wip.category = Notification.CATEGORY_TRANSPORT; 9246 } 9247 return wip; 9248 } 9249 9250 /** 9251 * @hide 9252 */ 9253 @Override makeContentView(boolean increasedHeight)9254 public RemoteViews makeContentView(boolean increasedHeight) { 9255 return makeMediaContentView(null /* customContent */); 9256 } 9257 9258 /** 9259 * @hide 9260 */ 9261 @Override makeBigContentView()9262 public RemoteViews makeBigContentView() { 9263 return makeMediaBigContentView(null /* customContent */); 9264 } 9265 9266 /** 9267 * @hide 9268 */ 9269 @Override makeHeadsUpContentView(boolean increasedHeight)9270 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9271 return makeMediaContentView(null /* customContent */); 9272 } 9273 9274 /** @hide */ 9275 @Override addExtras(Bundle extras)9276 public void addExtras(Bundle extras) { 9277 super.addExtras(extras); 9278 9279 if (mToken != null) { 9280 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 9281 } 9282 if (mActionsToShowInCompact != null) { 9283 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 9284 } 9285 if (mDeviceName != null) { 9286 extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName); 9287 } 9288 if (mDeviceIcon > 0) { 9289 extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon); 9290 } 9291 if (mDeviceIntent != null) { 9292 extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent); 9293 } 9294 } 9295 9296 /** 9297 * @hide 9298 */ 9299 @Override restoreFromExtras(Bundle extras)9300 protected void restoreFromExtras(Bundle extras) { 9301 super.restoreFromExtras(extras); 9302 9303 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 9304 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class); 9305 } 9306 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 9307 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 9308 } 9309 if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) { 9310 mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE); 9311 } 9312 if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) { 9313 mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON); 9314 } 9315 if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) { 9316 mDeviceIntent = extras.getParcelable( 9317 EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class); 9318 } 9319 } 9320 9321 /** 9322 * @hide 9323 */ 9324 @Override areNotificationsVisiblyDifferent(Style other)9325 public boolean areNotificationsVisiblyDifferent(Style other) { 9326 if (other == null || getClass() != other.getClass()) { 9327 return true; 9328 } 9329 // All fields to compare are on the Notification object 9330 return false; 9331 } 9332 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)9333 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 9334 Action action, StandardTemplateParams p) { 9335 final boolean tombstone = (action.actionIntent == null); 9336 container.setViewVisibility(buttonId, View.VISIBLE); 9337 container.setImageViewIcon(buttonId, action.getIcon()); 9338 9339 // If the action buttons should not be tinted, then just use the default 9340 // notification color. Otherwise, just use the passed-in color. 9341 int tintColor = mBuilder.getStandardActionColor(p); 9342 9343 container.setDrawableTint(buttonId, false, tintColor, 9344 PorterDuff.Mode.SRC_ATOP); 9345 9346 int rippleAlpha = mBuilder.getColors(p).getRippleAlpha(); 9347 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 9348 Color.blue(tintColor)); 9349 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 9350 9351 if (!tombstone) { 9352 container.setOnClickPendingIntent(buttonId, action.actionIntent); 9353 } 9354 container.setContentDescription(buttonId, action.title); 9355 } 9356 9357 /** @hide */ makeMediaContentView(@ullable RemoteViews customContent)9358 protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) { 9359 final int numActions = mBuilder.mActions.size(); 9360 final int numActionsToShow = Math.min(mActionsToShowInCompact == null 9361 ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 9362 if (numActionsToShow > numActions) { 9363 throw new IllegalArgumentException(String.format( 9364 "setShowActionsInCompactView: action %d out of bounds (max %d)", 9365 numActions, numActions - 1)); 9366 } 9367 9368 StandardTemplateParams p = mBuilder.mParams.reset() 9369 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 9370 .hideTime(numActionsToShow > 1) // hide if actions wider than a right icon 9371 .hideSubText(numActionsToShow > 1) // hide if actions wider than a right icon 9372 .hideLeftIcon(false) // allow large icon on left when grouped 9373 .hideRightIcon(numActionsToShow > 0) // right icon or actions; not both 9374 .hideProgress(true) 9375 .fillTextsFrom(mBuilder); 9376 TemplateBindResult result = new TemplateBindResult(); 9377 RemoteViews template = mBuilder.applyStandardTemplate( 9378 R.layout.notification_template_material_media, p, 9379 null /* result */); 9380 9381 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 9382 if (i < numActionsToShow) { 9383 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 9384 bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p); 9385 } else { 9386 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9387 } 9388 } 9389 // Prevent a swooping expand animation when there are no actions 9390 boolean hasActions = numActionsToShow != 0; 9391 template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE); 9392 9393 // Add custom view if provided by subclass. 9394 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9395 return template; 9396 } 9397 9398 /** @hide */ makeMediaBigContentView(@ullable RemoteViews customContent)9399 protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) { 9400 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 9401 StandardTemplateParams p = mBuilder.mParams.reset() 9402 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9403 .hideProgress(true) 9404 .fillTextsFrom(mBuilder); 9405 TemplateBindResult result = new TemplateBindResult(); 9406 RemoteViews template = mBuilder.applyStandardTemplate( 9407 R.layout.notification_template_material_big_media, p , result); 9408 9409 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 9410 if (i < actionCount) { 9411 bindMediaActionButton(template, 9412 MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 9413 } else { 9414 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9415 } 9416 } 9417 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9418 return template; 9419 } 9420 } 9421 9422 /** 9423 * Helper class for generating large-format notifications that include a large image attachment. 9424 * 9425 * Here's how you'd set the <code>CallStyle</code> on a notification: 9426 * <pre class="prettyprint"> 9427 * Notification notif = new Notification.Builder(mContext) 9428 * .setSmallIcon(R.drawable.new_post) 9429 * .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent)) 9430 * .build(); 9431 * </pre> 9432 */ 9433 public static class CallStyle extends Style { 9434 9435 /** 9436 * @hide 9437 */ 9438 @Retention(RetentionPolicy.SOURCE) 9439 @IntDef({ 9440 CALL_TYPE_UNKNOWN, 9441 CALL_TYPE_INCOMING, 9442 CALL_TYPE_ONGOING, 9443 CALL_TYPE_SCREENING 9444 }) 9445 public @interface CallType {}; 9446 9447 /** 9448 * Unknown call type. 9449 * 9450 * See {@link #EXTRA_CALL_TYPE}. 9451 */ 9452 public static final int CALL_TYPE_UNKNOWN = 0; 9453 9454 /** 9455 * Call type for incoming calls. 9456 * 9457 * See {@link #EXTRA_CALL_TYPE}. 9458 */ 9459 public static final int CALL_TYPE_INCOMING = 1; 9460 /** 9461 * Call type for ongoing calls. 9462 * 9463 * See {@link #EXTRA_CALL_TYPE}. 9464 */ 9465 public static final int CALL_TYPE_ONGOING = 2; 9466 /** 9467 * Call type for calls that are being screened. 9468 * 9469 * See {@link #EXTRA_CALL_TYPE}. 9470 */ 9471 public static final int CALL_TYPE_SCREENING = 3; 9472 9473 /** 9474 * This is a key used privately on the action.extras to give spacing priority 9475 * to the required call actions 9476 */ 9477 private static final String KEY_ACTION_PRIORITY = "key_action_priority"; 9478 9479 private int mCallType; 9480 private Person mPerson; 9481 private PendingIntent mAnswerIntent; 9482 private PendingIntent mDeclineIntent; 9483 private PendingIntent mHangUpIntent; 9484 private boolean mIsVideo; 9485 private Integer mAnswerButtonColor; 9486 private Integer mDeclineButtonColor; 9487 private Icon mVerificationIcon; 9488 private CharSequence mVerificationText; 9489 CallStyle()9490 CallStyle() { 9491 } 9492 9493 /** 9494 * Create a CallStyle for an incoming call. 9495 * This notification will have a decline and an answer action, will allow a single 9496 * custom {@link Builder#addAction(Action) action}, and will have a default 9497 * {@link Builder#setContentText(CharSequence) content text} for an incoming call. 9498 * 9499 * @param person The person displayed as the caller. 9500 * The person also needs to have a non-empty name associated with it. 9501 * @param declineIntent The intent to be sent when the user taps the decline action 9502 * @param answerIntent The intent to be sent when the user taps the answer action 9503 */ 9504 @NonNull forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9505 public static CallStyle forIncomingCall(@NonNull Person person, 9506 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) { 9507 return new CallStyle(CALL_TYPE_INCOMING, person, 9508 null /* hangUpIntent */, 9509 requireNonNull(declineIntent, "declineIntent is required"), 9510 requireNonNull(answerIntent, "answerIntent is required") 9511 ); 9512 } 9513 9514 /** 9515 * Create a CallStyle for an ongoing call. 9516 * This notification will have a hang up action, will allow up to two 9517 * custom {@link Builder#addAction(Action) actions}, and will have a default 9518 * {@link Builder#setContentText(CharSequence) content text} for an ongoing call. 9519 * 9520 * @param person The person displayed as being on the other end of the call. 9521 * The person also needs to have a non-empty name associated with it. 9522 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9523 */ 9524 @NonNull forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9525 public static CallStyle forOngoingCall(@NonNull Person person, 9526 @NonNull PendingIntent hangUpIntent) { 9527 return new CallStyle(CALL_TYPE_ONGOING, person, 9528 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9529 null /* declineIntent */, 9530 null /* answerIntent */ 9531 ); 9532 } 9533 9534 /** 9535 * Create a CallStyle for a call that is being screened. 9536 * This notification will have a hang up and an answer action, will allow a single 9537 * custom {@link Builder#addAction(Action) action}, and will have a default 9538 * {@link Builder#setContentText(CharSequence) content text} for a call that is being 9539 * screened. 9540 * 9541 * @param person The person displayed as the caller. 9542 * The person also needs to have a non-empty name associated with it. 9543 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9544 * @param answerIntent The intent to be sent when the user taps the answer action 9545 */ 9546 @NonNull forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9547 public static CallStyle forScreeningCall(@NonNull Person person, 9548 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) { 9549 return new CallStyle(CALL_TYPE_SCREENING, person, 9550 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9551 null /* declineIntent */, 9552 requireNonNull(answerIntent, "answerIntent is required") 9553 ); 9554 } 9555 9556 /** 9557 * @param callType The type of the call 9558 * @param person The person displayed for the incoming call. 9559 * The user also needs to have a non-empty name associated with it. 9560 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9561 * @param declineIntent The intent to be sent when the user taps the decline action 9562 * @param answerIntent The intent to be sent when the user taps the answer action 9563 */ CallStyle(@allType int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9564 private CallStyle(@CallType int callType, @NonNull Person person, 9565 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, 9566 @Nullable PendingIntent answerIntent) { 9567 if (person == null || TextUtils.isEmpty(person.getName())) { 9568 throw new IllegalArgumentException("person must have a non-empty a name"); 9569 } 9570 mCallType = callType; 9571 mPerson = person; 9572 mAnswerIntent = answerIntent; 9573 mDeclineIntent = declineIntent; 9574 mHangUpIntent = hangUpIntent; 9575 } 9576 9577 /** 9578 * Sets whether the call is a video call, which may affect the icons or text used on the 9579 * required action buttons. 9580 */ 9581 @NonNull setIsVideo(boolean isVideo)9582 public CallStyle setIsVideo(boolean isVideo) { 9583 mIsVideo = isVideo; 9584 return this; 9585 } 9586 9587 /** 9588 * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text} 9589 * as a verification status of the caller. 9590 */ 9591 @NonNull setVerificationIcon(@ullable Icon verificationIcon)9592 public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) { 9593 mVerificationIcon = verificationIcon; 9594 return this; 9595 } 9596 9597 /** 9598 * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon} 9599 * as a verification status of the caller. 9600 */ 9601 @NonNull setVerificationText(@ullable CharSequence verificationText)9602 public CallStyle setVerificationText(@Nullable CharSequence verificationText) { 9603 mVerificationText = safeCharSequence(verificationText); 9604 return this; 9605 } 9606 9607 /** 9608 * Optional color to be used as a hint for the Answer action button's color. 9609 * The system may change this color to ensure sufficient contrast with the background. 9610 * The system may choose to disregard this hint if the notification is not colorized. 9611 */ 9612 @NonNull setAnswerButtonColorHint(@olorInt int color)9613 public CallStyle setAnswerButtonColorHint(@ColorInt int color) { 9614 mAnswerButtonColor = color; 9615 return this; 9616 } 9617 9618 /** 9619 * Optional color to be used as a hint for the Decline or Hang Up action button's color. 9620 * The system may change this color to ensure sufficient contrast with the background. 9621 * The system may choose to disregard this hint if the notification is not colorized. 9622 */ 9623 @NonNull setDeclineButtonColorHint(@olorInt int color)9624 public CallStyle setDeclineButtonColorHint(@ColorInt int color) { 9625 mDeclineButtonColor = color; 9626 return this; 9627 } 9628 9629 /** @hide */ 9630 @Override buildStyled(Notification wip)9631 public Notification buildStyled(Notification wip) { 9632 wip = super.buildStyled(wip); 9633 // ensure that the actions in the builder and notification are corrected. 9634 mBuilder.mActions = getActionsListWithSystemActions(); 9635 wip.actions = new Action[mBuilder.mActions.size()]; 9636 mBuilder.mActions.toArray(wip.actions); 9637 return wip; 9638 } 9639 9640 /** 9641 * @hide 9642 */ displayCustomViewInline()9643 public boolean displayCustomViewInline() { 9644 // This is a lie; True is returned to make sure that the custom view is not used 9645 // instead of the template, but it will not actually be included. 9646 return true; 9647 } 9648 9649 /** 9650 * @hide 9651 */ 9652 @Override purgeResources()9653 public void purgeResources() { 9654 super.purgeResources(); 9655 if (mVerificationIcon != null) { 9656 mVerificationIcon.convertToAshmem(); 9657 } 9658 } 9659 9660 /** 9661 * @hide 9662 */ 9663 @Override reduceImageSizes(Context context)9664 public void reduceImageSizes(Context context) { 9665 super.reduceImageSizes(context); 9666 if (mVerificationIcon != null) { 9667 int rightIconSize = context.getResources().getDimensionPixelSize( 9668 ActivityManager.isLowRamDeviceStatic() 9669 ? R.dimen.notification_right_icon_size_low_ram 9670 : R.dimen.notification_right_icon_size); 9671 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 9672 } 9673 } 9674 9675 /** 9676 * @hide 9677 */ 9678 @Override makeContentView(boolean increasedHeight)9679 public RemoteViews makeContentView(boolean increasedHeight) { 9680 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL); 9681 } 9682 9683 /** 9684 * @hide 9685 */ 9686 @Override makeHeadsUpContentView(boolean increasedHeight)9687 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9688 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 9689 } 9690 9691 /** 9692 * @hide 9693 */ makeBigContentView()9694 public RemoteViews makeBigContentView() { 9695 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG); 9696 } 9697 9698 @NonNull makeNegativeAction()9699 private Action makeNegativeAction() { 9700 if (mDeclineIntent == null) { 9701 return makeAction(R.drawable.ic_call_decline, 9702 R.string.call_notification_hang_up_action, 9703 mDeclineButtonColor, R.color.call_notification_decline_color, 9704 mHangUpIntent); 9705 } else { 9706 return makeAction(R.drawable.ic_call_decline, 9707 R.string.call_notification_decline_action, 9708 mDeclineButtonColor, R.color.call_notification_decline_color, 9709 mDeclineIntent); 9710 } 9711 } 9712 9713 @Nullable makeAnswerAction()9714 private Action makeAnswerAction() { 9715 return mAnswerIntent == null ? null : makeAction( 9716 mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer, 9717 mIsVideo ? R.string.call_notification_answer_video_action 9718 : R.string.call_notification_answer_action, 9719 mAnswerButtonColor, R.color.call_notification_answer_color, 9720 mAnswerIntent); 9721 } 9722 9723 @NonNull makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9724 private Action makeAction(@DrawableRes int icon, @StringRes int title, 9725 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) { 9726 if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) { 9727 colorInt = mBuilder.mContext.getColor(defaultColorRes); 9728 } 9729 Action action = new Action.Builder(Icon.createWithResource("", icon), 9730 new SpannableStringBuilder().append(mBuilder.mContext.getString(title), 9731 new ForegroundColorSpan(colorInt), 9732 SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE), 9733 intent).build(); 9734 action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true); 9735 return action; 9736 } 9737 isActionAddedByCallStyle(Action action)9738 private boolean isActionAddedByCallStyle(Action action) { 9739 // This is an internal extra added by the style to these actions. If an app were to add 9740 // this extra to the action themselves, the action would be dropped. :shrug: 9741 return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY); 9742 } 9743 9744 /** 9745 * Gets the actions list for the call with the answer/decline/hangUp actions inserted in 9746 * the correct place. This returns the correct result even if the system actions have 9747 * already been added, and even if more actions were added since then. 9748 * @hide 9749 */ 9750 @NonNull getActionsListWithSystemActions()9751 public ArrayList<Action> getActionsListWithSystemActions() { 9752 // Define the system actions we expect to see 9753 final Action firstAction = makeNegativeAction(); 9754 final Action lastAction = makeAnswerAction(); 9755 9756 // Start creating the result list. 9757 int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS; 9758 ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS); 9759 9760 // Always have a first action. 9761 resultActions.add(firstAction); 9762 --nonContextualActionSlotsRemaining; 9763 9764 // Copy actions into the new list, correcting system actions. 9765 if (mBuilder.mActions != null) { 9766 for (Notification.Action action : mBuilder.mActions) { 9767 if (action.isContextual()) { 9768 // Always include all contextual actions 9769 resultActions.add(action); 9770 } else if (isActionAddedByCallStyle(action)) { 9771 // Drop any old versions of system actions 9772 } else { 9773 // Copy non-contextual actions; decrement the remaining action slots. 9774 resultActions.add(action); 9775 --nonContextualActionSlotsRemaining; 9776 } 9777 // If there's exactly one action slot left, fill it with the lastAction. 9778 if (lastAction != null && nonContextualActionSlotsRemaining == 1) { 9779 resultActions.add(lastAction); 9780 --nonContextualActionSlotsRemaining; 9781 } 9782 } 9783 } 9784 // If there are any action slots left, the lastAction still needs to be added. 9785 if (lastAction != null && nonContextualActionSlotsRemaining >= 1) { 9786 resultActions.add(lastAction); 9787 } 9788 return resultActions; 9789 } 9790 makeCallLayout(int viewType)9791 private RemoteViews makeCallLayout(int viewType) { 9792 final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL; 9793 Bundle extras = mBuilder.mN.extras; 9794 CharSequence title = mPerson != null ? mPerson.getName() : null; 9795 CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 9796 if (text == null) { 9797 text = getDefaultText(); 9798 } 9799 9800 // Bind standard template 9801 StandardTemplateParams p = mBuilder.mParams.reset() 9802 .viewType(viewType) 9803 .callStyleActions(true) 9804 .allowTextWithProgress(true) 9805 .hideLeftIcon(true) 9806 .hideRightIcon(true) 9807 .hideAppName(isCollapsed) 9808 .titleViewId(R.id.conversation_text) 9809 .title(title) 9810 .text(text) 9811 .summaryText(mBuilder.processLegacyText(mVerificationText)); 9812 mBuilder.mActions = getActionsListWithSystemActions(); 9813 final RemoteViews contentView; 9814 if (isCollapsed) { 9815 contentView = mBuilder.applyStandardTemplate( 9816 R.layout.notification_template_material_call, p, null /* result */); 9817 } else { 9818 contentView = mBuilder.applyStandardTemplateWithActions( 9819 R.layout.notification_template_material_big_call, p, null /* result */); 9820 } 9821 9822 // Bind some extra conversation-specific header fields. 9823 if (!p.mHideAppName) { 9824 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 9825 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE); 9826 } 9827 bindCallerVerification(contentView, p); 9828 9829 // Bind some custom CallLayout properties 9830 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 9831 mBuilder.getSmallIconColor(p)); 9832 contentView.setInt(R.id.status_bar_latest_event_content, 9833 "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); 9834 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 9835 mBuilder.mN.mLargeIcon); 9836 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 9837 mBuilder.mN.extras); 9838 9839 return contentView; 9840 } 9841 bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9842 private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) { 9843 String iconContentDescription = null; 9844 boolean showDivider = true; 9845 if (mVerificationIcon != null) { 9846 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon); 9847 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */, 9848 mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 9849 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE); 9850 iconContentDescription = mBuilder.mContext.getString( 9851 R.string.notification_verified_content_description); 9852 showDivider = false; // the icon replaces the divider 9853 } else { 9854 contentView.setViewVisibility(R.id.verification_icon, View.GONE); 9855 } 9856 if (!TextUtils.isEmpty(mVerificationText)) { 9857 contentView.setTextViewText(R.id.verification_text, mVerificationText); 9858 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p); 9859 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE); 9860 iconContentDescription = null; // let the app's text take precedence 9861 } else { 9862 contentView.setViewVisibility(R.id.verification_text, View.GONE); 9863 showDivider = false; // no divider if no text 9864 } 9865 contentView.setContentDescription(R.id.verification_icon, iconContentDescription); 9866 if (showDivider) { 9867 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE); 9868 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p); 9869 } else { 9870 contentView.setViewVisibility(R.id.verification_divider, View.GONE); 9871 } 9872 } 9873 9874 @Nullable getDefaultText()9875 private String getDefaultText() { 9876 switch (mCallType) { 9877 case CALL_TYPE_INCOMING: 9878 return mBuilder.mContext.getString(R.string.call_notification_incoming_text); 9879 case CALL_TYPE_ONGOING: 9880 return mBuilder.mContext.getString(R.string.call_notification_ongoing_text); 9881 case CALL_TYPE_SCREENING: 9882 return mBuilder.mContext.getString(R.string.call_notification_screening_text); 9883 } 9884 return null; 9885 } 9886 9887 /** 9888 * @hide 9889 */ addExtras(Bundle extras)9890 public void addExtras(Bundle extras) { 9891 super.addExtras(extras); 9892 extras.putInt(EXTRA_CALL_TYPE, mCallType); 9893 extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo); 9894 extras.putParcelable(EXTRA_CALL_PERSON, mPerson); 9895 if (mVerificationIcon != null) { 9896 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon); 9897 } 9898 if (mVerificationText != null) { 9899 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText); 9900 } 9901 if (mAnswerIntent != null) { 9902 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent); 9903 } 9904 if (mDeclineIntent != null) { 9905 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent); 9906 } 9907 if (mHangUpIntent != null) { 9908 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent); 9909 } 9910 if (mAnswerButtonColor != null) { 9911 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor); 9912 } 9913 if (mDeclineButtonColor != null) { 9914 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor); 9915 } 9916 fixTitleAndTextExtras(extras); 9917 } 9918 fixTitleAndTextExtras(Bundle extras)9919 private void fixTitleAndTextExtras(Bundle extras) { 9920 CharSequence sender = mPerson != null ? mPerson.getName() : null; 9921 if (sender != null) { 9922 extras.putCharSequence(EXTRA_TITLE, sender); 9923 } 9924 if (extras.getCharSequence(EXTRA_TEXT) == null) { 9925 extras.putCharSequence(EXTRA_TEXT, getDefaultText()); 9926 } 9927 } 9928 9929 /** 9930 * @hide 9931 */ 9932 @Override restoreFromExtras(Bundle extras)9933 protected void restoreFromExtras(Bundle extras) { 9934 super.restoreFromExtras(extras); 9935 mCallType = extras.getInt(EXTRA_CALL_TYPE); 9936 mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO); 9937 mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); 9938 mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON, android.graphics.drawable.Icon.class); 9939 mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT); 9940 mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class); 9941 mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class); 9942 mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class); 9943 mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR) 9944 ? extras.getInt(EXTRA_ANSWER_COLOR) : null; 9945 mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR) 9946 ? extras.getInt(EXTRA_DECLINE_COLOR) : null; 9947 } 9948 9949 /** 9950 * @hide 9951 */ 9952 @Override hasSummaryInHeader()9953 public boolean hasSummaryInHeader() { 9954 return false; 9955 } 9956 9957 /** 9958 * @hide 9959 */ 9960 @Override areNotificationsVisiblyDifferent(Style other)9961 public boolean areNotificationsVisiblyDifferent(Style other) { 9962 if (other == null || getClass() != other.getClass()) { 9963 return true; 9964 } 9965 CallStyle otherS = (CallStyle) other; 9966 return !Objects.equals(mCallType, otherS.mCallType) 9967 || !Objects.equals(mPerson, otherS.mPerson) 9968 || !Objects.equals(mVerificationText, otherS.mVerificationText); 9969 } 9970 } 9971 9972 /** 9973 * Notification style for custom views that are decorated by the system 9974 * 9975 * <p>Instead of providing a notification that is completely custom, a developer can set this 9976 * style and still obtain system decorations like the notification header with the expand 9977 * affordance and actions. 9978 * 9979 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 9980 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 9981 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 9982 * corresponding custom views to display. 9983 * 9984 * To use this style with your Notification, feed it to 9985 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9986 * <pre class="prettyprint"> 9987 * Notification noti = new Notification.Builder() 9988 * .setSmallIcon(R.drawable.ic_stat_player) 9989 * .setLargeIcon(albumArtBitmap)) 9990 * .setCustomContentView(contentView); 9991 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 9992 * .build(); 9993 * </pre> 9994 */ 9995 public static class DecoratedCustomViewStyle extends Style { 9996 DecoratedCustomViewStyle()9997 public DecoratedCustomViewStyle() { 9998 } 9999 10000 /** 10001 * @hide 10002 */ displayCustomViewInline()10003 public boolean displayCustomViewInline() { 10004 return true; 10005 } 10006 10007 /** 10008 * @hide 10009 */ 10010 @Override makeContentView(boolean increasedHeight)10011 public RemoteViews makeContentView(boolean increasedHeight) { 10012 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 10013 } 10014 10015 /** 10016 * @hide 10017 */ 10018 @Override makeBigContentView()10019 public RemoteViews makeBigContentView() { 10020 return makeDecoratedBigContentView(); 10021 } 10022 10023 /** 10024 * @hide 10025 */ 10026 @Override makeHeadsUpContentView(boolean increasedHeight)10027 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 10028 return makeDecoratedHeadsUpContentView(); 10029 } 10030 makeDecoratedHeadsUpContentView()10031 private RemoteViews makeDecoratedHeadsUpContentView() { 10032 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 10033 ? mBuilder.mN.contentView 10034 : mBuilder.mN.headsUpContentView; 10035 if (headsUpContentView == null) { 10036 return null; // no custom view; use the default behavior 10037 } 10038 if (mBuilder.mActions.size() == 0) { 10039 return makeStandardTemplateWithCustomContent(headsUpContentView); 10040 } 10041 TemplateBindResult result = new TemplateBindResult(); 10042 StandardTemplateParams p = mBuilder.mParams.reset() 10043 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 10044 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 10045 .fillTextsFrom(mBuilder); 10046 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 10047 mBuilder.getHeadsUpBaseLayoutResource(), p, result); 10048 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView, 10049 p, result); 10050 return remoteViews; 10051 } 10052 makeStandardTemplateWithCustomContent(RemoteViews customContent)10053 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 10054 if (customContent == null) { 10055 return null; // no custom view; use the default behavior 10056 } 10057 TemplateBindResult result = new TemplateBindResult(); 10058 StandardTemplateParams p = mBuilder.mParams.reset() 10059 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 10060 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 10061 .fillTextsFrom(mBuilder); 10062 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 10063 mBuilder.getBaseLayoutResource(), p, result); 10064 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent, 10065 p, result); 10066 return remoteViews; 10067 } 10068 makeDecoratedBigContentView()10069 private RemoteViews makeDecoratedBigContentView() { 10070 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 10071 ? mBuilder.mN.contentView 10072 : mBuilder.mN.bigContentView; 10073 if (bigContentView == null) { 10074 return null; // no custom view; use the default behavior 10075 } 10076 TemplateBindResult result = new TemplateBindResult(); 10077 StandardTemplateParams p = mBuilder.mParams.reset() 10078 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 10079 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 10080 .fillTextsFrom(mBuilder); 10081 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 10082 mBuilder.getBigBaseLayoutResource(), p, result); 10083 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView, 10084 p, result); 10085 return remoteViews; 10086 } 10087 10088 /** 10089 * @hide 10090 */ 10091 @Override areNotificationsVisiblyDifferent(Style other)10092 public boolean areNotificationsVisiblyDifferent(Style other) { 10093 if (other == null || getClass() != other.getClass()) { 10094 return true; 10095 } 10096 // Comparison done for all custom RemoteViews, independent of style 10097 return false; 10098 } 10099 } 10100 10101 /** 10102 * Notification style for media custom views that are decorated by the system 10103 * 10104 * <p>Instead of providing a media notification that is completely custom, a developer can set 10105 * this style and still obtain system decorations like the notification header with the expand 10106 * affordance and actions. 10107 * 10108 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 10109 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 10110 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 10111 * corresponding custom views to display. 10112 * <p> 10113 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 10114 * notification by using {@link Notification.Builder#setColorized(boolean)}. 10115 * <p> 10116 * To use this style with your Notification, feed it to 10117 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 10118 * <pre class="prettyprint"> 10119 * Notification noti = new Notification.Builder() 10120 * .setSmallIcon(R.drawable.ic_stat_player) 10121 * .setLargeIcon(albumArtBitmap)) 10122 * .setCustomContentView(contentView); 10123 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 10124 * .setMediaSession(mySession)) 10125 * .build(); 10126 * </pre> 10127 * 10128 * @see android.app.Notification.DecoratedCustomViewStyle 10129 * @see android.app.Notification.MediaStyle 10130 */ 10131 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 10132 DecoratedMediaCustomViewStyle()10133 public DecoratedMediaCustomViewStyle() { 10134 } 10135 10136 /** 10137 * @hide 10138 */ displayCustomViewInline()10139 public boolean displayCustomViewInline() { 10140 return true; 10141 } 10142 10143 /** 10144 * @hide 10145 */ 10146 @Override makeContentView(boolean increasedHeight)10147 public RemoteViews makeContentView(boolean increasedHeight) { 10148 return makeMediaContentView(mBuilder.mN.contentView); 10149 } 10150 10151 /** 10152 * @hide 10153 */ 10154 @Override makeBigContentView()10155 public RemoteViews makeBigContentView() { 10156 RemoteViews customContent = mBuilder.mN.bigContentView != null 10157 ? mBuilder.mN.bigContentView 10158 : mBuilder.mN.contentView; 10159 return makeMediaBigContentView(customContent); 10160 } 10161 10162 /** 10163 * @hide 10164 */ 10165 @Override makeHeadsUpContentView(boolean increasedHeight)10166 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 10167 RemoteViews customContent = mBuilder.mN.headsUpContentView != null 10168 ? mBuilder.mN.headsUpContentView 10169 : mBuilder.mN.contentView; 10170 return makeMediaBigContentView(customContent); 10171 } 10172 10173 /** 10174 * @hide 10175 */ 10176 @Override areNotificationsVisiblyDifferent(Style other)10177 public boolean areNotificationsVisiblyDifferent(Style other) { 10178 if (other == null || getClass() != other.getClass()) { 10179 return true; 10180 } 10181 // Comparison done for all custom RemoteViews, independent of style 10182 return false; 10183 } 10184 } 10185 10186 /** 10187 * Encapsulates the information needed to display a notification as a bubble. 10188 * 10189 * <p>A bubble is used to display app content in a floating window over the existing 10190 * foreground activity. A bubble has a collapsed state represented by an icon and an 10191 * expanded state that displays an activity. These may be defined via 10192 * {@link Builder#Builder(PendingIntent, Icon)} or they may 10193 * be defined via an existing shortcut using {@link Builder#Builder(String)}. 10194 * </p> 10195 * 10196 * <b>Notifications with a valid and allowed bubble will display in collapsed state 10197 * outside of the notification shade on unlocked devices. When a user interacts with the 10198 * collapsed bubble, the bubble activity will be invoked and displayed.</b> 10199 * 10200 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 10201 */ 10202 public static final class BubbleMetadata implements Parcelable { 10203 10204 private PendingIntent mPendingIntent; 10205 private PendingIntent mDeleteIntent; 10206 private Icon mIcon; 10207 private int mDesiredHeight; 10208 @DimenRes private int mDesiredHeightResId; 10209 private int mFlags; 10210 private String mShortcutId; 10211 10212 /** 10213 * If set and the app creating the bubble is in the foreground, the bubble will be posted 10214 * in its expanded state. 10215 * 10216 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 10217 * The app is considered foreground if it is visible and on the screen, note that 10218 * a foreground service does not qualify. 10219 * </p> 10220 * 10221 * <p>Generally this flag should only be set if the user has performed an action to request 10222 * or create a bubble.</p> 10223 * 10224 * @hide 10225 */ 10226 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 10227 10228 /** 10229 * Indicates whether the notification associated with the bubble is being visually 10230 * suppressed from the notification shade. When <code>true</code> the notification is 10231 * hidden, when <code>false</code> the notification shows as normal. 10232 * 10233 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 10234 * the associated notification in the notification shade.</p> 10235 * 10236 * <p>Generally this flag should only be set by the app if the user has performed an 10237 * action to request or create a bubble, or if the user has seen the content in the 10238 * notification and the notification is no longer relevant. </p> 10239 * 10240 * <p>The system will also update this flag with <code>true</code> to hide the notification 10241 * from the user once the bubble has been expanded. </p> 10242 * 10243 * @hide 10244 */ 10245 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 10246 10247 /** 10248 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 10249 * user is viewing the same content outside of the bubble. For example, the user has a 10250 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10251 * 10252 * @hide 10253 */ 10254 public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004; 10255 10256 /** 10257 * Indicates whether the bubble is visually suppressed from the bubble stack. 10258 * 10259 * @hide 10260 */ 10261 public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008; 10262 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)10263 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 10264 Icon icon, int height, @DimenRes int heightResId, String shortcutId) { 10265 mPendingIntent = expandIntent; 10266 mIcon = icon; 10267 mDesiredHeight = height; 10268 mDesiredHeightResId = heightResId; 10269 mDeleteIntent = deleteIntent; 10270 mShortcutId = shortcutId; 10271 } 10272 BubbleMetadata(Parcel in)10273 private BubbleMetadata(Parcel in) { 10274 if (in.readInt() != 0) { 10275 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 10276 } 10277 if (in.readInt() != 0) { 10278 mIcon = Icon.CREATOR.createFromParcel(in); 10279 } 10280 mDesiredHeight = in.readInt(); 10281 mFlags = in.readInt(); 10282 if (in.readInt() != 0) { 10283 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 10284 } 10285 mDesiredHeightResId = in.readInt(); 10286 if (in.readInt() != 0) { 10287 mShortcutId = in.readString8(); 10288 } 10289 } 10290 10291 /** 10292 * @return the shortcut id used for this bubble if created via 10293 * {@link Builder#Builder(String)} or null if created 10294 * via {@link Builder#Builder(PendingIntent, Icon)}. 10295 */ 10296 @Nullable getShortcutId()10297 public String getShortcutId() { 10298 return mShortcutId; 10299 } 10300 10301 /** 10302 * @return the pending intent used to populate the floating window for this bubble, or 10303 * null if this bubble is created via {@link Builder#Builder(String)}. 10304 */ 10305 @SuppressLint("InvalidNullConversion") 10306 @Nullable getIntent()10307 public PendingIntent getIntent() { 10308 return mPendingIntent; 10309 } 10310 10311 /** 10312 * @deprecated use {@link #getIntent()} instead. 10313 * @removed Removed from the R SDK but was never publicly stable. 10314 */ 10315 @Nullable 10316 @Deprecated getBubbleIntent()10317 public PendingIntent getBubbleIntent() { 10318 return mPendingIntent; 10319 } 10320 10321 /** 10322 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 10323 */ 10324 @Nullable getDeleteIntent()10325 public PendingIntent getDeleteIntent() { 10326 return mDeleteIntent; 10327 } 10328 10329 /** 10330 * @return the icon that will be displayed for this bubble when it is collapsed, or null 10331 * if the bubble is created via {@link Builder#Builder(String)}. 10332 */ 10333 @SuppressLint("InvalidNullConversion") 10334 @Nullable getIcon()10335 public Icon getIcon() { 10336 return mIcon; 10337 } 10338 10339 /** 10340 * @deprecated use {@link #getIcon()} instead. 10341 * @removed Removed from the R SDK but was never publicly stable. 10342 */ 10343 @Nullable 10344 @Deprecated getBubbleIcon()10345 public Icon getBubbleIcon() { 10346 return mIcon; 10347 } 10348 10349 /** 10350 * @return the ideal height, in DPs, for the floating window that app content defined by 10351 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has 10352 * not been set. 10353 */ 10354 @Dimension(unit = DP) getDesiredHeight()10355 public int getDesiredHeight() { 10356 return mDesiredHeight; 10357 } 10358 10359 /** 10360 * @return the resId of ideal height for the floating window that app content defined by 10361 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 10362 * been provided for the desired height. 10363 */ 10364 @DimenRes getDesiredHeightResId()10365 public int getDesiredHeightResId() { 10366 return mDesiredHeightResId; 10367 } 10368 10369 /** 10370 * @return whether this bubble should auto expand when it is posted. 10371 * 10372 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 10373 */ getAutoExpandBubble()10374 public boolean getAutoExpandBubble() { 10375 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 10376 } 10377 10378 /** 10379 * Indicates whether the notification associated with the bubble is being visually 10380 * suppressed from the notification shade. When <code>true</code> the notification is 10381 * hidden, when <code>false</code> the notification shows as normal. 10382 * 10383 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 10384 * the associated notification in the notification shade.</p> 10385 * 10386 * <p>Generally the app should only set this flag if the user has performed an 10387 * action to request or create a bubble, or if the user has seen the content in the 10388 * notification and the notification is no longer relevant. </p> 10389 * 10390 * <p>The system will update this flag with <code>true</code> to hide the notification 10391 * from the user once the bubble has been expanded.</p> 10392 * 10393 * @return whether this bubble should suppress the notification when it is posted. 10394 * 10395 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 10396 */ isNotificationSuppressed()10397 public boolean isNotificationSuppressed() { 10398 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 10399 } 10400 10401 /** 10402 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 10403 * user is viewing the same content outside of the bubble. For example, the user has a 10404 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10405 * 10406 * To match the activity and the bubble notification, the bubble notification should 10407 * have a locus id set that matches a locus id set on the activity. 10408 * 10409 * @return whether this bubble should be suppressed when the same content is visible 10410 * outside of the bubble. 10411 * 10412 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10413 */ isBubbleSuppressable()10414 public boolean isBubbleSuppressable() { 10415 return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0; 10416 } 10417 10418 /** 10419 * Indicates whether the bubble is currently visually suppressed from the bubble stack. 10420 * 10421 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10422 */ isBubbleSuppressed()10423 public boolean isBubbleSuppressed() { 10424 return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0; 10425 } 10426 10427 /** 10428 * Sets whether the notification associated with the bubble is being visually 10429 * suppressed from the notification shade. When <code>true</code> the notification is 10430 * hidden, when <code>false</code> the notification shows as normal. 10431 * 10432 * @hide 10433 */ setSuppressNotification(boolean suppressed)10434 public void setSuppressNotification(boolean suppressed) { 10435 if (suppressed) { 10436 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10437 } else { 10438 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10439 } 10440 } 10441 10442 /** 10443 * Sets whether the bubble should be visually suppressed from the bubble stack if the 10444 * user is viewing the same content outside of the bubble. For example, the user has a 10445 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10446 * 10447 * @hide 10448 */ setSuppressBubble(boolean suppressed)10449 public void setSuppressBubble(boolean suppressed) { 10450 if (suppressed) { 10451 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10452 } else { 10453 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10454 } 10455 } 10456 10457 /** 10458 * @hide 10459 */ setFlags(int flags)10460 public void setFlags(int flags) { 10461 mFlags = flags; 10462 } 10463 10464 /** 10465 * @hide 10466 */ getFlags()10467 public int getFlags() { 10468 return mFlags; 10469 } 10470 10471 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 10472 new Parcelable.Creator<BubbleMetadata>() { 10473 10474 @Override 10475 public BubbleMetadata createFromParcel(Parcel source) { 10476 return new BubbleMetadata(source); 10477 } 10478 10479 @Override 10480 public BubbleMetadata[] newArray(int size) { 10481 return new BubbleMetadata[size]; 10482 } 10483 }; 10484 10485 @Override describeContents()10486 public int describeContents() { 10487 return 0; 10488 } 10489 10490 @Override writeToParcel(Parcel out, int flags)10491 public void writeToParcel(Parcel out, int flags) { 10492 out.writeInt(mPendingIntent != null ? 1 : 0); 10493 if (mPendingIntent != null) { 10494 mPendingIntent.writeToParcel(out, 0); 10495 } 10496 out.writeInt(mIcon != null ? 1 : 0); 10497 if (mIcon != null) { 10498 mIcon.writeToParcel(out, 0); 10499 } 10500 out.writeInt(mDesiredHeight); 10501 out.writeInt(mFlags); 10502 out.writeInt(mDeleteIntent != null ? 1 : 0); 10503 if (mDeleteIntent != null) { 10504 mDeleteIntent.writeToParcel(out, 0); 10505 } 10506 out.writeInt(mDesiredHeightResId); 10507 out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); 10508 if (!TextUtils.isEmpty(mShortcutId)) { 10509 out.writeString8(mShortcutId); 10510 } 10511 } 10512 10513 /** 10514 * Builder to construct a {@link BubbleMetadata} object. 10515 */ 10516 public static final class Builder { 10517 10518 private PendingIntent mPendingIntent; 10519 private Icon mIcon; 10520 private int mDesiredHeight; 10521 @DimenRes private int mDesiredHeightResId; 10522 private int mFlags; 10523 private PendingIntent mDeleteIntent; 10524 private String mShortcutId; 10525 10526 /** 10527 * @deprecated use {@link Builder#Builder(String)} for a bubble created via a 10528 * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble 10529 * created via a {@link PendingIntent}. 10530 */ 10531 @Deprecated Builder()10532 public Builder() { 10533 } 10534 10535 /** 10536 * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create 10537 * a shortcut bubble, ensure that the shortcut associated with the provided 10538 * {@param shortcutId} is published as a dynamic shortcut that was built with 10539 * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your 10540 * notification will not be able to bubble. 10541 * 10542 * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p> 10543 * 10544 * <p>The shortcut activity will be used when the bubble is expanded. This will display 10545 * the shortcut activity in a floating window over the existing foreground activity.</p> 10546 * 10547 * <p>When the activity is launched from a bubble, 10548 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10549 * </p> 10550 * 10551 * <p>If the shortcut has not been published when the bubble notification is sent, 10552 * no bubble will be produced. If the shortcut is deleted while the bubble is active, 10553 * the bubble will be removed.</p> 10554 * 10555 * @throws NullPointerException if shortcutId is null. 10556 * 10557 * @see ShortcutInfo 10558 * @see ShortcutInfo.Builder#setLongLived(boolean) 10559 * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List) 10560 */ Builder(@onNull String shortcutId)10561 public Builder(@NonNull String shortcutId) { 10562 if (TextUtils.isEmpty(shortcutId)) { 10563 throw new NullPointerException("Bubble requires a non-null shortcut id"); 10564 } 10565 mShortcutId = shortcutId; 10566 } 10567 10568 /** 10569 * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon. 10570 * 10571 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10572 * should be representative of the content within the bubble. If your app produces 10573 * multiple bubbles, the icon should be unique for each of them.</p> 10574 * 10575 * <p>The intent that will be used when the bubble is expanded. This will display the 10576 * app content in a floating window over the existing foreground activity. The intent 10577 * should point to a resizable activity. </p> 10578 * 10579 * <p>When the activity is launched from a bubble, 10580 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10581 * </p> 10582 * 10583 * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE. 10584 * 10585 * @throws NullPointerException if intent is null. 10586 * @throws NullPointerException if icon is null. 10587 */ Builder(@onNull PendingIntent intent, @NonNull Icon icon)10588 public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) { 10589 if (intent == null) { 10590 throw new NullPointerException("Bubble requires non-null pending intent"); 10591 } 10592 if (icon == null) { 10593 throw new NullPointerException("Bubbles require non-null icon"); 10594 } 10595 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10596 && icon.getType() != TYPE_URI) { 10597 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10598 + "TYPE_URI_ADAPTIVE_BITMAP. " 10599 + "In the future, using an icon of this type will be required."); 10600 } 10601 mPendingIntent = intent; 10602 mIcon = icon; 10603 } 10604 10605 /** 10606 * @deprecated use {@link Builder#Builder(String)} instead. 10607 * @removed Removed from the R SDK but was never publicly stable. 10608 */ 10609 @NonNull 10610 @Deprecated createShortcutBubble(@onNull String shortcutId)10611 public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) { 10612 if (!TextUtils.isEmpty(shortcutId)) { 10613 // If shortcut id is set, we don't use these if they were previously set. 10614 mPendingIntent = null; 10615 mIcon = null; 10616 } 10617 mShortcutId = shortcutId; 10618 return this; 10619 } 10620 10621 /** 10622 * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead. 10623 * @removed Removed from the R SDK but was never publicly stable. 10624 */ 10625 @NonNull 10626 @Deprecated createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10627 public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent, 10628 @NonNull Icon icon) { 10629 if (intent == null) { 10630 throw new IllegalArgumentException("Bubble requires non-null pending intent"); 10631 } 10632 if (icon == null) { 10633 throw new IllegalArgumentException("Bubbles require non-null icon"); 10634 } 10635 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10636 && icon.getType() != TYPE_URI) { 10637 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10638 + "TYPE_URI_ADAPTIVE_BITMAP. " 10639 + "In the future, using an icon of this type will be required."); 10640 } 10641 mShortcutId = null; 10642 mPendingIntent = intent; 10643 mIcon = icon; 10644 return this; 10645 } 10646 10647 /** 10648 * Sets the intent for the bubble. 10649 * 10650 * <p>The intent that will be used when the bubble is expanded. This will display the 10651 * app content in a floating window over the existing foreground activity. The intent 10652 * should point to a resizable activity. </p> 10653 * 10654 * @throws NullPointerException if intent is null. 10655 * @throws IllegalStateException if this builder was created via 10656 * {@link Builder#Builder(String)}. 10657 */ 10658 @NonNull setIntent(@onNull PendingIntent intent)10659 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 10660 if (mShortcutId != null) { 10661 throw new IllegalStateException("Created as a shortcut bubble, cannot set a " 10662 + "PendingIntent. Consider using " 10663 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10664 } 10665 if (intent == null) { 10666 throw new NullPointerException("Bubble requires non-null pending intent"); 10667 } 10668 mPendingIntent = intent; 10669 return this; 10670 } 10671 10672 /** 10673 * Sets the icon for the bubble. Can only be used if the bubble was created 10674 * via {@link Builder#Builder(PendingIntent, Icon)}. 10675 * 10676 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10677 * should be representative of the content within the bubble. If your app produces 10678 * multiple bubbles, the icon should be unique for each of them.</p> 10679 * 10680 * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI} 10681 * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p> 10682 * 10683 * @throws NullPointerException if icon is null. 10684 * @throws IllegalStateException if this builder was created via 10685 * {@link Builder#Builder(String)}. 10686 */ 10687 @NonNull setIcon(@onNull Icon icon)10688 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 10689 if (mShortcutId != null) { 10690 throw new IllegalStateException("Created as a shortcut bubble, cannot set an " 10691 + "Icon. Consider using " 10692 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10693 } 10694 if (icon == null) { 10695 throw new NullPointerException("Bubbles require non-null icon"); 10696 } 10697 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10698 && icon.getType() != TYPE_URI) { 10699 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10700 + "TYPE_URI_ADAPTIVE_BITMAP. " 10701 + "In the future, using an icon of this type will be required."); 10702 } 10703 mIcon = icon; 10704 return this; 10705 } 10706 10707 /** 10708 * Sets the desired height in DPs for the expanded content of the bubble. 10709 * 10710 * <p>This height may not be respected if there is not enough space on the screen or if 10711 * the provided height is too small to be useful.</p> 10712 * 10713 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 10714 * previous value set will be cleared after calling this method, and this value will 10715 * be used instead.</p> 10716 * 10717 * <p>A desired height (in DPs or via resID) is optional.</p> 10718 * 10719 * @see #setDesiredHeightResId(int) 10720 */ 10721 @NonNull setDesiredHeight(@imensionunit = DP) int height)10722 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 10723 mDesiredHeight = Math.max(height, 0); 10724 mDesiredHeightResId = 0; 10725 return this; 10726 } 10727 10728 10729 /** 10730 * Sets the desired height via resId for the expanded content of the bubble. 10731 * 10732 * <p>This height may not be respected if there is not enough space on the screen or if 10733 * the provided height is too small to be useful.</p> 10734 * 10735 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 10736 * previous value set will be cleared after calling this method, and this value will 10737 * be used instead.</p> 10738 * 10739 * <p>A desired height (in DPs or via resID) is optional.</p> 10740 * 10741 * @see #setDesiredHeight(int) 10742 */ 10743 @NonNull setDesiredHeightResId(@imenRes int heightResId)10744 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 10745 mDesiredHeightResId = heightResId; 10746 mDesiredHeight = 0; 10747 return this; 10748 } 10749 10750 /** 10751 * Sets whether the bubble will be posted in its expanded state. 10752 * 10753 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 10754 * The app is considered foreground if it is visible and on the screen, note that 10755 * a foreground service does not qualify. 10756 * </p> 10757 * 10758 * <p>Generally, this flag should only be set if the user has performed an action to 10759 * request or create a bubble.</p> 10760 * 10761 * <p>Setting this flag is optional; it defaults to false.</p> 10762 */ 10763 @NonNull setAutoExpandBubble(boolean shouldExpand)10764 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 10765 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 10766 return this; 10767 } 10768 10769 /** 10770 * Sets whether the bubble will be posted <b>without</b> the associated notification in 10771 * the notification shade. 10772 * 10773 * <p>Generally, this flag should only be set if the user has performed an action to 10774 * request or create a bubble, or if the user has seen the content in the notification 10775 * and the notification is no longer relevant.</p> 10776 * 10777 * <p>Setting this flag is optional; it defaults to false.</p> 10778 */ 10779 @NonNull setSuppressNotification(boolean shouldSuppressNotif)10780 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 10781 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 10782 return this; 10783 } 10784 10785 /** 10786 * Indicates whether the bubble should be visually suppressed from the bubble stack if 10787 * the user is viewing the same content outside of the bubble. For example, the user has 10788 * a bubble with Alice and then opens up the main app and navigates to Alice's page. 10789 * 10790 * To match the activity and the bubble notification, the bubble notification should 10791 * have a locus id set that matches a locus id set on the activity. 10792 * 10793 * {@link Notification.Builder#setLocusId(LocusId)} 10794 * {@link Activity#setLocusContext(LocusId, Bundle)} 10795 */ 10796 @NonNull setSuppressableBubble(boolean suppressBubble)10797 public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) { 10798 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble); 10799 return this; 10800 } 10801 10802 /** 10803 * Sets an intent to send when this bubble is explicitly removed by the user. 10804 * 10805 * <p>Setting a delete intent is optional.</p> 10806 */ 10807 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)10808 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 10809 mDeleteIntent = deleteIntent; 10810 return this; 10811 } 10812 10813 /** 10814 * Creates the {@link BubbleMetadata} defined by this builder. 10815 * 10816 * @throws NullPointerException if required elements have not been set. 10817 */ 10818 @NonNull build()10819 public BubbleMetadata build() { 10820 if (mShortcutId == null && mPendingIntent == null) { 10821 throw new NullPointerException( 10822 "Must supply pending intent or shortcut to bubble"); 10823 } 10824 if (mShortcutId == null && mIcon == null) { 10825 throw new NullPointerException( 10826 "Must supply an icon or shortcut for the bubble"); 10827 } 10828 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 10829 mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId); 10830 data.setFlags(mFlags); 10831 return data; 10832 } 10833 10834 /** 10835 * @hide 10836 */ setFlag(int mask, boolean value)10837 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 10838 if (value) { 10839 mFlags |= mask; 10840 } else { 10841 mFlags &= ~mask; 10842 } 10843 return this; 10844 } 10845 } 10846 } 10847 10848 10849 // When adding a new Style subclass here, don't forget to update 10850 // Builder.getNotificationStyleClass. 10851 10852 /** 10853 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 10854 * metadata or change options on a notification builder. 10855 */ 10856 public interface Extender { 10857 /** 10858 * Apply this extender to a notification builder. 10859 * @param builder the builder to be modified. 10860 * @return the build object for chaining. 10861 */ extend(Builder builder)10862 public Builder extend(Builder builder); 10863 } 10864 10865 /** 10866 * Helper class to add wearable extensions to notifications. 10867 * <p class="note"> See 10868 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 10869 * for Android Wear</a> for more information on how to use this class. 10870 * <p> 10871 * To create a notification with wearable extensions: 10872 * <ol> 10873 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 10874 * properties. 10875 * <li>Create a {@link android.app.Notification.WearableExtender}. 10876 * <li>Set wearable-specific properties using the 10877 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 10878 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 10879 * notification. 10880 * <li>Post the notification to the notification system with the 10881 * {@code NotificationManager.notify(...)} methods. 10882 * </ol> 10883 * 10884 * <pre class="prettyprint"> 10885 * Notification notif = new Notification.Builder(mContext) 10886 * .setContentTitle("New mail from " + sender.toString()) 10887 * .setContentText(subject) 10888 * .setSmallIcon(R.drawable.new_mail) 10889 * .extend(new Notification.WearableExtender() 10890 * .setContentIcon(R.drawable.new_mail)) 10891 * .build(); 10892 * NotificationManager notificationManger = 10893 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 10894 * notificationManger.notify(0, notif);</pre> 10895 * 10896 * <p>Wearable extensions can be accessed on an existing notification by using the 10897 * {@code WearableExtender(Notification)} constructor, 10898 * and then using the {@code get} methods to access values. 10899 * 10900 * <pre class="prettyprint"> 10901 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 10902 * notification); 10903 * List<Notification> pages = wearableExtender.getPages();</pre> 10904 */ 10905 public static final class WearableExtender implements Extender { 10906 /** 10907 * Sentinel value for an action index that is unset. 10908 */ 10909 public static final int UNSET_ACTION_INDEX = -1; 10910 10911 /** 10912 * Size value for use with {@link #setCustomSizePreset} to show this notification with 10913 * default sizing. 10914 * <p>For custom display notifications created using {@link #setDisplayIntent}, 10915 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 10916 * on their content. 10917 * 10918 * @deprecated Display intents are no longer supported. 10919 */ 10920 @Deprecated 10921 public static final int SIZE_DEFAULT = 0; 10922 10923 /** 10924 * Size value for use with {@link #setCustomSizePreset} to show this notification 10925 * with an extra small size. 10926 * <p>This value is only applicable for custom display notifications created using 10927 * {@link #setDisplayIntent}. 10928 * 10929 * @deprecated Display intents are no longer supported. 10930 */ 10931 @Deprecated 10932 public static final int SIZE_XSMALL = 1; 10933 10934 /** 10935 * Size value for use with {@link #setCustomSizePreset} to show this notification 10936 * with a small size. 10937 * <p>This value is only applicable for custom display notifications created using 10938 * {@link #setDisplayIntent}. 10939 * 10940 * @deprecated Display intents are no longer supported. 10941 */ 10942 @Deprecated 10943 public static final int SIZE_SMALL = 2; 10944 10945 /** 10946 * Size value for use with {@link #setCustomSizePreset} to show this notification 10947 * with a medium size. 10948 * <p>This value is only applicable for custom display notifications created using 10949 * {@link #setDisplayIntent}. 10950 * 10951 * @deprecated Display intents are no longer supported. 10952 */ 10953 @Deprecated 10954 public static final int SIZE_MEDIUM = 3; 10955 10956 /** 10957 * Size value for use with {@link #setCustomSizePreset} to show this notification 10958 * with a large size. 10959 * <p>This value is only applicable for custom display notifications created using 10960 * {@link #setDisplayIntent}. 10961 * 10962 * @deprecated Display intents are no longer supported. 10963 */ 10964 @Deprecated 10965 public static final int SIZE_LARGE = 4; 10966 10967 /** 10968 * Size value for use with {@link #setCustomSizePreset} to show this notification 10969 * full screen. 10970 * <p>This value is only applicable for custom display notifications created using 10971 * {@link #setDisplayIntent}. 10972 * 10973 * @deprecated Display intents are no longer supported. 10974 */ 10975 @Deprecated 10976 public static final int SIZE_FULL_SCREEN = 5; 10977 10978 /** 10979 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 10980 * short amount of time when this notification is displayed on the screen. This 10981 * is the default value. 10982 * 10983 * @deprecated This feature is no longer supported. 10984 */ 10985 @Deprecated 10986 public static final int SCREEN_TIMEOUT_SHORT = 0; 10987 10988 /** 10989 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 10990 * for a longer amount of time when this notification is displayed on the screen. 10991 * 10992 * @deprecated This feature is no longer supported. 10993 */ 10994 @Deprecated 10995 public static final int SCREEN_TIMEOUT_LONG = -1; 10996 10997 /** Notification extra which contains wearable extensions */ 10998 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 10999 11000 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 11001 private static final String KEY_ACTIONS = "actions"; 11002 private static final String KEY_FLAGS = "flags"; 11003 static final String KEY_DISPLAY_INTENT = "displayIntent"; 11004 private static final String KEY_PAGES = "pages"; 11005 static final String KEY_BACKGROUND = "background"; 11006 private static final String KEY_CONTENT_ICON = "contentIcon"; 11007 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 11008 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 11009 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 11010 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 11011 private static final String KEY_GRAVITY = "gravity"; 11012 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 11013 private static final String KEY_DISMISSAL_ID = "dismissalId"; 11014 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 11015 11016 // Flags bitwise-ored to mFlags 11017 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 11018 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 11019 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 11020 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 11021 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 11022 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 11023 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 11024 11025 // Default value for flags integer 11026 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 11027 11028 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 11029 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 11030 11031 private ArrayList<Action> mActions = new ArrayList<Action>(); 11032 private int mFlags = DEFAULT_FLAGS; 11033 private PendingIntent mDisplayIntent; 11034 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 11035 private Bitmap mBackground; 11036 private int mContentIcon; 11037 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 11038 private int mContentActionIndex = UNSET_ACTION_INDEX; 11039 private int mCustomSizePreset = SIZE_DEFAULT; 11040 private int mCustomContentHeight; 11041 private int mGravity = DEFAULT_GRAVITY; 11042 private int mHintScreenTimeout; 11043 private String mDismissalId; 11044 private String mBridgeTag; 11045 11046 /** 11047 * Create a {@link android.app.Notification.WearableExtender} with default 11048 * options. 11049 */ WearableExtender()11050 public WearableExtender() { 11051 } 11052 WearableExtender(Notification notif)11053 public WearableExtender(Notification notif) { 11054 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 11055 if (wearableBundle != null) { 11056 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class); 11057 if (actions != null) { 11058 mActions.addAll(actions); 11059 } 11060 11061 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 11062 mDisplayIntent = wearableBundle.getParcelable( 11063 KEY_DISPLAY_INTENT, PendingIntent.class); 11064 11065 Notification[] pages = getParcelableArrayFromBundle( 11066 wearableBundle, KEY_PAGES, Notification.class); 11067 if (pages != null) { 11068 Collections.addAll(mPages, pages); 11069 } 11070 11071 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class); 11072 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 11073 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 11074 DEFAULT_CONTENT_ICON_GRAVITY); 11075 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 11076 UNSET_ACTION_INDEX); 11077 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 11078 SIZE_DEFAULT); 11079 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 11080 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 11081 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 11082 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 11083 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 11084 } 11085 } 11086 11087 /** 11088 * Apply wearable extensions to a notification that is being built. This is typically 11089 * called by the {@link android.app.Notification.Builder#extend} method of 11090 * {@link android.app.Notification.Builder}. 11091 */ 11092 @Override extend(Notification.Builder builder)11093 public Notification.Builder extend(Notification.Builder builder) { 11094 Bundle wearableBundle = new Bundle(); 11095 11096 if (!mActions.isEmpty()) { 11097 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 11098 } 11099 if (mFlags != DEFAULT_FLAGS) { 11100 wearableBundle.putInt(KEY_FLAGS, mFlags); 11101 } 11102 if (mDisplayIntent != null) { 11103 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 11104 } 11105 if (!mPages.isEmpty()) { 11106 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 11107 new Notification[mPages.size()])); 11108 } 11109 if (mBackground != null) { 11110 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 11111 } 11112 if (mContentIcon != 0) { 11113 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 11114 } 11115 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 11116 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 11117 } 11118 if (mContentActionIndex != UNSET_ACTION_INDEX) { 11119 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 11120 mContentActionIndex); 11121 } 11122 if (mCustomSizePreset != SIZE_DEFAULT) { 11123 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 11124 } 11125 if (mCustomContentHeight != 0) { 11126 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 11127 } 11128 if (mGravity != DEFAULT_GRAVITY) { 11129 wearableBundle.putInt(KEY_GRAVITY, mGravity); 11130 } 11131 if (mHintScreenTimeout != 0) { 11132 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 11133 } 11134 if (mDismissalId != null) { 11135 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 11136 } 11137 if (mBridgeTag != null) { 11138 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 11139 } 11140 11141 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 11142 return builder; 11143 } 11144 11145 @Override clone()11146 public WearableExtender clone() { 11147 WearableExtender that = new WearableExtender(); 11148 that.mActions = new ArrayList<Action>(this.mActions); 11149 that.mFlags = this.mFlags; 11150 that.mDisplayIntent = this.mDisplayIntent; 11151 that.mPages = new ArrayList<Notification>(this.mPages); 11152 that.mBackground = this.mBackground; 11153 that.mContentIcon = this.mContentIcon; 11154 that.mContentIconGravity = this.mContentIconGravity; 11155 that.mContentActionIndex = this.mContentActionIndex; 11156 that.mCustomSizePreset = this.mCustomSizePreset; 11157 that.mCustomContentHeight = this.mCustomContentHeight; 11158 that.mGravity = this.mGravity; 11159 that.mHintScreenTimeout = this.mHintScreenTimeout; 11160 that.mDismissalId = this.mDismissalId; 11161 that.mBridgeTag = this.mBridgeTag; 11162 return that; 11163 } 11164 11165 /** 11166 * Add a wearable action to this notification. 11167 * 11168 * <p>When wearable actions are added using this method, the set of actions that 11169 * show on a wearable device splits from devices that only show actions added 11170 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 11171 * of which actions display on different devices. 11172 * 11173 * @param action the action to add to this notification 11174 * @return this object for method chaining 11175 * @see android.app.Notification.Action 11176 */ addAction(Action action)11177 public WearableExtender addAction(Action action) { 11178 mActions.add(action); 11179 return this; 11180 } 11181 11182 /** 11183 * Adds wearable actions to this notification. 11184 * 11185 * <p>When wearable actions are added using this method, the set of actions that 11186 * show on a wearable device splits from devices that only show actions added 11187 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 11188 * of which actions display on different devices. 11189 * 11190 * @param actions the actions to add to this notification 11191 * @return this object for method chaining 11192 * @see android.app.Notification.Action 11193 */ addActions(List<Action> actions)11194 public WearableExtender addActions(List<Action> actions) { 11195 mActions.addAll(actions); 11196 return this; 11197 } 11198 11199 /** 11200 * Clear all wearable actions present on this builder. 11201 * @return this object for method chaining. 11202 * @see #addAction 11203 */ clearActions()11204 public WearableExtender clearActions() { 11205 mActions.clear(); 11206 return this; 11207 } 11208 11209 /** 11210 * Get the wearable actions present on this notification. 11211 */ getActions()11212 public List<Action> getActions() { 11213 return mActions; 11214 } 11215 11216 /** 11217 * Set an intent to launch inside of an activity view when displaying 11218 * this notification. The {@link PendingIntent} provided should be for an activity. 11219 * 11220 * <pre class="prettyprint"> 11221 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 11222 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 11223 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 11224 * Notification notif = new Notification.Builder(context) 11225 * .extend(new Notification.WearableExtender() 11226 * .setDisplayIntent(displayPendingIntent) 11227 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 11228 * .build();</pre> 11229 * 11230 * <p>The activity to launch needs to allow embedding, must be exported, and 11231 * should have an empty task affinity. It is also recommended to use the device 11232 * default light theme. 11233 * 11234 * <p>Example AndroidManifest.xml entry: 11235 * <pre class="prettyprint"> 11236 * <activity android:name="com.example.MyDisplayActivity" 11237 * android:exported="true" 11238 * android:allowEmbedded="true" 11239 * android:taskAffinity="" 11240 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 11241 * 11242 * @param intent the {@link PendingIntent} for an activity 11243 * @return this object for method chaining 11244 * @see android.app.Notification.WearableExtender#getDisplayIntent 11245 * @deprecated Display intents are no longer supported. 11246 */ 11247 @Deprecated setDisplayIntent(PendingIntent intent)11248 public WearableExtender setDisplayIntent(PendingIntent intent) { 11249 mDisplayIntent = intent; 11250 return this; 11251 } 11252 11253 /** 11254 * Get the intent to launch inside of an activity view when displaying this 11255 * notification. This {@code PendingIntent} should be for an activity. 11256 * 11257 * @deprecated Display intents are no longer supported. 11258 */ 11259 @Deprecated getDisplayIntent()11260 public PendingIntent getDisplayIntent() { 11261 return mDisplayIntent; 11262 } 11263 11264 /** 11265 * Add an additional page of content to display with this notification. The current 11266 * notification forms the first page, and pages added using this function form 11267 * subsequent pages. This field can be used to separate a notification into multiple 11268 * sections. 11269 * 11270 * @param page the notification to add as another page 11271 * @return this object for method chaining 11272 * @see android.app.Notification.WearableExtender#getPages 11273 * @deprecated Multiple content pages are no longer supported. 11274 */ 11275 @Deprecated addPage(Notification page)11276 public WearableExtender addPage(Notification page) { 11277 mPages.add(page); 11278 return this; 11279 } 11280 11281 /** 11282 * Add additional pages of content to display with this notification. The current 11283 * notification forms the first page, and pages added using this function form 11284 * subsequent pages. This field can be used to separate a notification into multiple 11285 * sections. 11286 * 11287 * @param pages a list of notifications 11288 * @return this object for method chaining 11289 * @see android.app.Notification.WearableExtender#getPages 11290 * @deprecated Multiple content pages are no longer supported. 11291 */ 11292 @Deprecated addPages(List<Notification> pages)11293 public WearableExtender addPages(List<Notification> pages) { 11294 mPages.addAll(pages); 11295 return this; 11296 } 11297 11298 /** 11299 * Clear all additional pages present on this builder. 11300 * @return this object for method chaining. 11301 * @see #addPage 11302 * @deprecated Multiple content pages are no longer supported. 11303 */ 11304 @Deprecated clearPages()11305 public WearableExtender clearPages() { 11306 mPages.clear(); 11307 return this; 11308 } 11309 11310 /** 11311 * Get the array of additional pages of content for displaying this notification. The 11312 * current notification forms the first page, and elements within this array form 11313 * subsequent pages. This field can be used to separate a notification into multiple 11314 * sections. 11315 * @return the pages for this notification 11316 * @deprecated Multiple content pages are no longer supported. 11317 */ 11318 @Deprecated getPages()11319 public List<Notification> getPages() { 11320 return mPages; 11321 } 11322 11323 /** 11324 * Set a background image to be displayed behind the notification content. 11325 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 11326 * will work with any notification style. 11327 * 11328 * @param background the background bitmap 11329 * @return this object for method chaining 11330 * @see android.app.Notification.WearableExtender#getBackground 11331 * @deprecated Background images are no longer supported. 11332 */ 11333 @Deprecated setBackground(Bitmap background)11334 public WearableExtender setBackground(Bitmap background) { 11335 mBackground = background; 11336 return this; 11337 } 11338 11339 /** 11340 * Get a background image to be displayed behind the notification content. 11341 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 11342 * will work with any notification style. 11343 * 11344 * @return the background image 11345 * @see android.app.Notification.WearableExtender#setBackground 11346 * @deprecated Background images are no longer supported. 11347 */ 11348 @Deprecated getBackground()11349 public Bitmap getBackground() { 11350 return mBackground; 11351 } 11352 11353 /** 11354 * Set an icon that goes with the content of this notification. 11355 */ 11356 @Deprecated setContentIcon(int icon)11357 public WearableExtender setContentIcon(int icon) { 11358 mContentIcon = icon; 11359 return this; 11360 } 11361 11362 /** 11363 * Get an icon that goes with the content of this notification. 11364 */ 11365 @Deprecated getContentIcon()11366 public int getContentIcon() { 11367 return mContentIcon; 11368 } 11369 11370 /** 11371 * Set the gravity that the content icon should have within the notification display. 11372 * Supported values include {@link android.view.Gravity#START} and 11373 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 11374 * @see #setContentIcon 11375 */ 11376 @Deprecated setContentIconGravity(int contentIconGravity)11377 public WearableExtender setContentIconGravity(int contentIconGravity) { 11378 mContentIconGravity = contentIconGravity; 11379 return this; 11380 } 11381 11382 /** 11383 * Get the gravity that the content icon should have within the notification display. 11384 * Supported values include {@link android.view.Gravity#START} and 11385 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 11386 * @see #getContentIcon 11387 */ 11388 @Deprecated getContentIconGravity()11389 public int getContentIconGravity() { 11390 return mContentIconGravity; 11391 } 11392 11393 /** 11394 * Set an action from this notification's actions as the primary action. If the action has a 11395 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 11396 * directly on the notification. 11397 * 11398 * @param actionIndex The index of the primary action. 11399 * If wearable actions were added to the main notification, this index 11400 * will apply to that list, otherwise it will apply to the regular 11401 * actions list. 11402 */ setContentAction(int actionIndex)11403 public WearableExtender setContentAction(int actionIndex) { 11404 mContentActionIndex = actionIndex; 11405 return this; 11406 } 11407 11408 /** 11409 * Get the index of the notification action, if any, that was specified as the primary 11410 * action. 11411 * 11412 * <p>If wearable specific actions were added to the main notification, this index will 11413 * apply to that list, otherwise it will apply to the regular actions list. 11414 * 11415 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 11416 */ getContentAction()11417 public int getContentAction() { 11418 return mContentActionIndex; 11419 } 11420 11421 /** 11422 * Set the gravity that this notification should have within the available viewport space. 11423 * Supported values include {@link android.view.Gravity#TOP}, 11424 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11425 * The default value is {@link android.view.Gravity#BOTTOM}. 11426 */ 11427 @Deprecated setGravity(int gravity)11428 public WearableExtender setGravity(int gravity) { 11429 mGravity = gravity; 11430 return this; 11431 } 11432 11433 /** 11434 * Get the gravity that this notification should have within the available viewport space. 11435 * Supported values include {@link android.view.Gravity#TOP}, 11436 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11437 * The default value is {@link android.view.Gravity#BOTTOM}. 11438 */ 11439 @Deprecated getGravity()11440 public int getGravity() { 11441 return mGravity; 11442 } 11443 11444 /** 11445 * Set the custom size preset for the display of this notification out of the available 11446 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11447 * {@link #SIZE_LARGE}. 11448 * <p>Some custom size presets are only applicable for custom display notifications created 11449 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 11450 * documentation for the preset in question. See also 11451 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 11452 */ 11453 @Deprecated setCustomSizePreset(int sizePreset)11454 public WearableExtender setCustomSizePreset(int sizePreset) { 11455 mCustomSizePreset = sizePreset; 11456 return this; 11457 } 11458 11459 /** 11460 * Get the custom size preset for the display of this notification out of the available 11461 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11462 * {@link #SIZE_LARGE}. 11463 * <p>Some custom size presets are only applicable for custom display notifications created 11464 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 11465 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 11466 */ 11467 @Deprecated getCustomSizePreset()11468 public int getCustomSizePreset() { 11469 return mCustomSizePreset; 11470 } 11471 11472 /** 11473 * Set the custom height in pixels for the display of this notification's content. 11474 * <p>This option is only available for custom display notifications created 11475 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 11476 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 11477 * {@link #getCustomContentHeight}. 11478 */ 11479 @Deprecated setCustomContentHeight(int height)11480 public WearableExtender setCustomContentHeight(int height) { 11481 mCustomContentHeight = height; 11482 return this; 11483 } 11484 11485 /** 11486 * Get the custom height in pixels for the display of this notification's content. 11487 * <p>This option is only available for custom display notifications created 11488 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 11489 * {@link #setCustomContentHeight}. 11490 */ 11491 @Deprecated getCustomContentHeight()11492 public int getCustomContentHeight() { 11493 return mCustomContentHeight; 11494 } 11495 11496 /** 11497 * Set whether the scrolling position for the contents of this notification should start 11498 * at the bottom of the contents instead of the top when the contents are too long to 11499 * display within the screen. Default is false (start scroll at the top). 11500 */ setStartScrollBottom(boolean startScrollBottom)11501 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 11502 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 11503 return this; 11504 } 11505 11506 /** 11507 * Get whether the scrolling position for the contents of this notification should start 11508 * at the bottom of the contents instead of the top when the contents are too long to 11509 * display within the screen. Default is false (start scroll at the top). 11510 */ getStartScrollBottom()11511 public boolean getStartScrollBottom() { 11512 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 11513 } 11514 11515 /** 11516 * Set whether the content intent is available when the wearable device is not connected 11517 * to a companion device. The user can still trigger this intent when the wearable device 11518 * is offline, but a visual hint will indicate that the content intent may not be available. 11519 * Defaults to true. 11520 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11521 public WearableExtender setContentIntentAvailableOffline( 11522 boolean contentIntentAvailableOffline) { 11523 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 11524 return this; 11525 } 11526 11527 /** 11528 * Get whether the content intent is available when the wearable device is not connected 11529 * to a companion device. The user can still trigger this intent when the wearable device 11530 * is offline, but a visual hint will indicate that the content intent may not be available. 11531 * Defaults to true. 11532 */ getContentIntentAvailableOffline()11533 public boolean getContentIntentAvailableOffline() { 11534 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 11535 } 11536 11537 /** 11538 * Set a hint that this notification's icon should not be displayed. 11539 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 11540 * @return this object for method chaining 11541 */ 11542 @Deprecated setHintHideIcon(boolean hintHideIcon)11543 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 11544 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 11545 return this; 11546 } 11547 11548 /** 11549 * Get a hint that this notification's icon should not be displayed. 11550 * @return {@code true} if this icon should not be displayed, false otherwise. 11551 * The default value is {@code false} if this was never set. 11552 */ 11553 @Deprecated getHintHideIcon()11554 public boolean getHintHideIcon() { 11555 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 11556 } 11557 11558 /** 11559 * Set a visual hint that only the background image of this notification should be 11560 * displayed, and other semantic content should be hidden. This hint is only applicable 11561 * to sub-pages added using {@link #addPage}. 11562 */ 11563 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11564 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 11565 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 11566 return this; 11567 } 11568 11569 /** 11570 * Get a visual hint that only the background image of this notification should be 11571 * displayed, and other semantic content should be hidden. This hint is only applicable 11572 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 11573 */ 11574 @Deprecated getHintShowBackgroundOnly()11575 public boolean getHintShowBackgroundOnly() { 11576 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 11577 } 11578 11579 /** 11580 * Set a hint that this notification's background should not be clipped if possible, 11581 * and should instead be resized to fully display on the screen, retaining the aspect 11582 * ratio of the image. This can be useful for images like barcodes or qr codes. 11583 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 11584 * @return this object for method chaining 11585 */ 11586 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11587 public WearableExtender setHintAvoidBackgroundClipping( 11588 boolean hintAvoidBackgroundClipping) { 11589 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 11590 return this; 11591 } 11592 11593 /** 11594 * Get a hint that this notification's background should not be clipped if possible, 11595 * and should instead be resized to fully display on the screen, retaining the aspect 11596 * ratio of the image. This can be useful for images like barcodes or qr codes. 11597 * @return {@code true} if it's ok if the background is clipped on the screen, false 11598 * otherwise. The default value is {@code false} if this was never set. 11599 */ 11600 @Deprecated getHintAvoidBackgroundClipping()11601 public boolean getHintAvoidBackgroundClipping() { 11602 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 11603 } 11604 11605 /** 11606 * Set a hint that the screen should remain on for at least this duration when 11607 * this notification is displayed on the screen. 11608 * @param timeout The requested screen timeout in milliseconds. Can also be either 11609 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11610 * @return this object for method chaining 11611 */ 11612 @Deprecated setHintScreenTimeout(int timeout)11613 public WearableExtender setHintScreenTimeout(int timeout) { 11614 mHintScreenTimeout = timeout; 11615 return this; 11616 } 11617 11618 /** 11619 * Get the duration, in milliseconds, that the screen should remain on for 11620 * when this notification is displayed. 11621 * @return the duration in milliseconds if > 0, or either one of the sentinel values 11622 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11623 */ 11624 @Deprecated getHintScreenTimeout()11625 public int getHintScreenTimeout() { 11626 return mHintScreenTimeout; 11627 } 11628 11629 /** 11630 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 11631 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11632 * qr codes, as well as other simple black-and-white tickets. 11633 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 11634 * @return this object for method chaining 11635 * @deprecated This feature is no longer supported. 11636 */ 11637 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)11638 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 11639 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 11640 return this; 11641 } 11642 11643 /** 11644 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 11645 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11646 * qr codes, as well as other simple black-and-white tickets. 11647 * @return {@code true} if it should be displayed in ambient, false otherwise 11648 * otherwise. The default value is {@code false} if this was never set. 11649 * @deprecated This feature is no longer supported. 11650 */ 11651 @Deprecated getHintAmbientBigPicture()11652 public boolean getHintAmbientBigPicture() { 11653 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 11654 } 11655 11656 /** 11657 * Set a hint that this notification's content intent will launch an {@link Activity} 11658 * directly, telling the platform that it can generate the appropriate transitions. 11659 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 11660 * an activity and transitions should be generated, false otherwise. 11661 * @return this object for method chaining 11662 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11663 public WearableExtender setHintContentIntentLaunchesActivity( 11664 boolean hintContentIntentLaunchesActivity) { 11665 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 11666 return this; 11667 } 11668 11669 /** 11670 * Get a hint that this notification's content intent will launch an {@link Activity} 11671 * directly, telling the platform that it can generate the appropriate transitions 11672 * @return {@code true} if the content intent will launch an activity and transitions should 11673 * be generated, false otherwise. The default value is {@code false} if this was never set. 11674 */ getHintContentIntentLaunchesActivity()11675 public boolean getHintContentIntentLaunchesActivity() { 11676 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 11677 } 11678 11679 /** 11680 * Sets the dismissal id for this notification. If a notification is posted with a 11681 * dismissal id, then when that notification is canceled, notifications on other wearables 11682 * and the paired Android phone having that same dismissal id will also be canceled. See 11683 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 11684 * Notifications</a> for more information. 11685 * @param dismissalId the dismissal id of the notification. 11686 * @return this object for method chaining 11687 */ setDismissalId(String dismissalId)11688 public WearableExtender setDismissalId(String dismissalId) { 11689 mDismissalId = dismissalId; 11690 return this; 11691 } 11692 11693 /** 11694 * Returns the dismissal id of the notification. 11695 * @return the dismissal id of the notification or null if it has not been set. 11696 */ getDismissalId()11697 public String getDismissalId() { 11698 return mDismissalId; 11699 } 11700 11701 /** 11702 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 11703 * posted from a phone to provide finer-grained control on what notifications are bridged 11704 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 11705 * Features to Notifications</a> for more information. 11706 * @param bridgeTag the bridge tag of the notification. 11707 * @return this object for method chaining 11708 */ setBridgeTag(String bridgeTag)11709 public WearableExtender setBridgeTag(String bridgeTag) { 11710 mBridgeTag = bridgeTag; 11711 return this; 11712 } 11713 11714 /** 11715 * Returns the bridge tag of the notification. 11716 * @return the bridge tag or null if not present. 11717 */ getBridgeTag()11718 public String getBridgeTag() { 11719 return mBridgeTag; 11720 } 11721 setFlag(int mask, boolean value)11722 private void setFlag(int mask, boolean value) { 11723 if (value) { 11724 mFlags |= mask; 11725 } else { 11726 mFlags &= ~mask; 11727 } 11728 } 11729 visitUris(@onNull Consumer<Uri> visitor)11730 private void visitUris(@NonNull Consumer<Uri> visitor) { 11731 for (Action action : mActions) { 11732 action.visitUris(visitor); 11733 } 11734 } 11735 } 11736 11737 /** 11738 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 11739 * with car extensions: 11740 * 11741 * <ol> 11742 * <li>Create an {@link Notification.Builder}, setting any desired 11743 * properties. 11744 * <li>Create a {@link CarExtender}. 11745 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 11746 * {@link CarExtender}. 11747 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 11748 * to apply the extensions to a notification. 11749 * </ol> 11750 * 11751 * <pre class="prettyprint"> 11752 * Notification notification = new Notification.Builder(context) 11753 * ... 11754 * .extend(new CarExtender() 11755 * .set*(...)) 11756 * .build(); 11757 * </pre> 11758 * 11759 * <p>Car extensions can be accessed on an existing notification by using the 11760 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 11761 * to access values. 11762 */ 11763 public static final class CarExtender implements Extender { 11764 private static final String TAG = "CarExtender"; 11765 11766 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 11767 private static final String EXTRA_LARGE_ICON = "large_icon"; 11768 private static final String EXTRA_CONVERSATION = "car_conversation"; 11769 private static final String EXTRA_COLOR = "app_color"; 11770 11771 private Bitmap mLargeIcon; 11772 private UnreadConversation mUnreadConversation; 11773 private int mColor = Notification.COLOR_DEFAULT; 11774 11775 /** 11776 * Create a {@link CarExtender} with default options. 11777 */ CarExtender()11778 public CarExtender() { 11779 } 11780 11781 /** 11782 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 11783 * 11784 * @param notif The notification from which to copy options. 11785 */ CarExtender(Notification notif)11786 public CarExtender(Notification notif) { 11787 Bundle carBundle = notif.extras == null ? 11788 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 11789 if (carBundle != null) { 11790 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class); 11791 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 11792 11793 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 11794 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 11795 } 11796 } 11797 11798 /** 11799 * Apply car extensions to a notification that is being built. This is typically called by 11800 * the {@link Notification.Builder#extend(Notification.Extender)} 11801 * method of {@link Notification.Builder}. 11802 */ 11803 @Override extend(Notification.Builder builder)11804 public Notification.Builder extend(Notification.Builder builder) { 11805 Bundle carExtensions = new Bundle(); 11806 11807 if (mLargeIcon != null) { 11808 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 11809 } 11810 if (mColor != Notification.COLOR_DEFAULT) { 11811 carExtensions.putInt(EXTRA_COLOR, mColor); 11812 } 11813 11814 if (mUnreadConversation != null) { 11815 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 11816 carExtensions.putBundle(EXTRA_CONVERSATION, b); 11817 } 11818 11819 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 11820 return builder; 11821 } 11822 11823 /** 11824 * Sets the accent color to use when Android Auto presents the notification. 11825 * 11826 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 11827 * to accent the displayed notification. However, not all colors are acceptable in an 11828 * automotive setting. This method can be used to override the color provided in the 11829 * notification in such a situation. 11830 */ setColor(@olorInt int color)11831 public CarExtender setColor(@ColorInt int color) { 11832 mColor = color; 11833 return this; 11834 } 11835 11836 /** 11837 * Gets the accent color. 11838 * 11839 * @see #setColor 11840 */ 11841 @ColorInt getColor()11842 public int getColor() { 11843 return mColor; 11844 } 11845 11846 /** 11847 * Sets the large icon of the car notification. 11848 * 11849 * If no large icon is set in the extender, Android Auto will display the icon 11850 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 11851 * 11852 * @param largeIcon The large icon to use in the car notification. 11853 * @return This object for method chaining. 11854 */ setLargeIcon(Bitmap largeIcon)11855 public CarExtender setLargeIcon(Bitmap largeIcon) { 11856 mLargeIcon = largeIcon; 11857 return this; 11858 } 11859 11860 /** 11861 * Gets the large icon used in this car notification, or null if no icon has been set. 11862 * 11863 * @return The large icon for the car notification. 11864 * @see CarExtender#setLargeIcon 11865 */ getLargeIcon()11866 public Bitmap getLargeIcon() { 11867 return mLargeIcon; 11868 } 11869 11870 /** 11871 * Sets the unread conversation in a message notification. 11872 * 11873 * @param unreadConversation The unread part of the conversation this notification conveys. 11874 * @return This object for method chaining. 11875 */ setUnreadConversation(UnreadConversation unreadConversation)11876 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 11877 mUnreadConversation = unreadConversation; 11878 return this; 11879 } 11880 11881 /** 11882 * Returns the unread conversation conveyed by this notification. 11883 * @see #setUnreadConversation(UnreadConversation) 11884 */ getUnreadConversation()11885 public UnreadConversation getUnreadConversation() { 11886 return mUnreadConversation; 11887 } 11888 11889 /** 11890 * A class which holds the unread messages from a conversation. 11891 */ 11892 public static class UnreadConversation { 11893 private static final String KEY_AUTHOR = "author"; 11894 private static final String KEY_TEXT = "text"; 11895 private static final String KEY_MESSAGES = "messages"; 11896 static final String KEY_REMOTE_INPUT = "remote_input"; 11897 static final String KEY_ON_REPLY = "on_reply"; 11898 static final String KEY_ON_READ = "on_read"; 11899 private static final String KEY_PARTICIPANTS = "participants"; 11900 private static final String KEY_TIMESTAMP = "timestamp"; 11901 11902 private final String[] mMessages; 11903 private final RemoteInput mRemoteInput; 11904 private final PendingIntent mReplyPendingIntent; 11905 private final PendingIntent mReadPendingIntent; 11906 private final String[] mParticipants; 11907 private final long mLatestTimestamp; 11908 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11909 UnreadConversation(String[] messages, RemoteInput remoteInput, 11910 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 11911 String[] participants, long latestTimestamp) { 11912 mMessages = messages; 11913 mRemoteInput = remoteInput; 11914 mReadPendingIntent = readPendingIntent; 11915 mReplyPendingIntent = replyPendingIntent; 11916 mParticipants = participants; 11917 mLatestTimestamp = latestTimestamp; 11918 } 11919 11920 /** 11921 * Gets the list of messages conveyed by this notification. 11922 */ getMessages()11923 public String[] getMessages() { 11924 return mMessages; 11925 } 11926 11927 /** 11928 * Gets the remote input that will be used to convey the response to a message list, or 11929 * null if no such remote input exists. 11930 */ getRemoteInput()11931 public RemoteInput getRemoteInput() { 11932 return mRemoteInput; 11933 } 11934 11935 /** 11936 * Gets the pending intent that will be triggered when the user replies to this 11937 * notification. 11938 */ getReplyPendingIntent()11939 public PendingIntent getReplyPendingIntent() { 11940 return mReplyPendingIntent; 11941 } 11942 11943 /** 11944 * Gets the pending intent that Android Auto will send after it reads aloud all messages 11945 * in this object's message list. 11946 */ getReadPendingIntent()11947 public PendingIntent getReadPendingIntent() { 11948 return mReadPendingIntent; 11949 } 11950 11951 /** 11952 * Gets the participants in the conversation. 11953 */ getParticipants()11954 public String[] getParticipants() { 11955 return mParticipants; 11956 } 11957 11958 /** 11959 * Gets the firs participant in the conversation. 11960 */ getParticipant()11961 public String getParticipant() { 11962 return mParticipants.length > 0 ? mParticipants[0] : null; 11963 } 11964 11965 /** 11966 * Gets the timestamp of the conversation. 11967 */ getLatestTimestamp()11968 public long getLatestTimestamp() { 11969 return mLatestTimestamp; 11970 } 11971 getBundleForUnreadConversation()11972 Bundle getBundleForUnreadConversation() { 11973 Bundle b = new Bundle(); 11974 String author = null; 11975 if (mParticipants != null && mParticipants.length > 1) { 11976 author = mParticipants[0]; 11977 } 11978 Parcelable[] messages = new Parcelable[mMessages.length]; 11979 for (int i = 0; i < messages.length; i++) { 11980 Bundle m = new Bundle(); 11981 m.putString(KEY_TEXT, mMessages[i]); 11982 m.putString(KEY_AUTHOR, author); 11983 messages[i] = m; 11984 } 11985 b.putParcelableArray(KEY_MESSAGES, messages); 11986 if (mRemoteInput != null) { 11987 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 11988 } 11989 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 11990 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 11991 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 11992 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 11993 return b; 11994 } 11995 getUnreadConversationFromBundle(Bundle b)11996 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 11997 if (b == null) { 11998 return null; 11999 } 12000 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES, 12001 Parcelable.class); 12002 String[] messages = null; 12003 if (parcelableMessages != null) { 12004 String[] tmp = new String[parcelableMessages.length]; 12005 boolean success = true; 12006 for (int i = 0; i < tmp.length; i++) { 12007 if (!(parcelableMessages[i] instanceof Bundle)) { 12008 success = false; 12009 break; 12010 } 12011 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 12012 if (tmp[i] == null) { 12013 success = false; 12014 break; 12015 } 12016 } 12017 if (success) { 12018 messages = tmp; 12019 } else { 12020 return null; 12021 } 12022 } 12023 12024 PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class); 12025 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class); 12026 12027 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class); 12028 12029 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 12030 if (participants == null || participants.length != 1) { 12031 return null; 12032 } 12033 12034 return new UnreadConversation(messages, 12035 remoteInput, 12036 onReply, 12037 onRead, 12038 participants, b.getLong(KEY_TIMESTAMP)); 12039 } 12040 }; 12041 12042 /** 12043 * Builder class for {@link CarExtender.UnreadConversation} objects. 12044 */ 12045 public static class Builder { 12046 private final List<String> mMessages = new ArrayList<String>(); 12047 private final String mParticipant; 12048 private RemoteInput mRemoteInput; 12049 private PendingIntent mReadPendingIntent; 12050 private PendingIntent mReplyPendingIntent; 12051 private long mLatestTimestamp; 12052 12053 /** 12054 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 12055 * 12056 * @param name The name of the other participant in the conversation. 12057 */ Builder(String name)12058 public Builder(String name) { 12059 mParticipant = name; 12060 } 12061 12062 /** 12063 * Appends a new unread message to the list of messages for this conversation. 12064 * 12065 * The messages should be added from oldest to newest. 12066 * 12067 * @param message The text of the new unread message. 12068 * @return This object for method chaining. 12069 */ addMessage(String message)12070 public Builder addMessage(String message) { 12071 mMessages.add(message); 12072 return this; 12073 } 12074 12075 /** 12076 * Sets the pending intent and remote input which will convey the reply to this 12077 * notification. 12078 * 12079 * @param pendingIntent The pending intent which will be triggered on a reply. 12080 * @param remoteInput The remote input parcelable which will carry the reply. 12081 * @return This object for method chaining. 12082 * 12083 * @see CarExtender.UnreadConversation#getRemoteInput 12084 * @see CarExtender.UnreadConversation#getReplyPendingIntent 12085 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)12086 public Builder setReplyAction( 12087 PendingIntent pendingIntent, RemoteInput remoteInput) { 12088 mRemoteInput = remoteInput; 12089 mReplyPendingIntent = pendingIntent; 12090 12091 return this; 12092 } 12093 12094 /** 12095 * Sets the pending intent that will be sent once the messages in this notification 12096 * are read. 12097 * 12098 * @param pendingIntent The pending intent to use. 12099 * @return This object for method chaining. 12100 */ setReadPendingIntent(PendingIntent pendingIntent)12101 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 12102 mReadPendingIntent = pendingIntent; 12103 return this; 12104 } 12105 12106 /** 12107 * Sets the timestamp of the most recent message in an unread conversation. 12108 * 12109 * If a messaging notification has been posted by your application and has not 12110 * yet been cancelled, posting a later notification with the same id and tag 12111 * but without a newer timestamp may result in Android Auto not displaying a 12112 * heads up notification for the later notification. 12113 * 12114 * @param timestamp The timestamp of the most recent message in the conversation. 12115 * @return This object for method chaining. 12116 */ setLatestTimestamp(long timestamp)12117 public Builder setLatestTimestamp(long timestamp) { 12118 mLatestTimestamp = timestamp; 12119 return this; 12120 } 12121 12122 /** 12123 * Builds a new unread conversation object. 12124 * 12125 * @return The new unread conversation object. 12126 */ build()12127 public UnreadConversation build() { 12128 String[] messages = mMessages.toArray(new String[mMessages.size()]); 12129 String[] participants = { mParticipant }; 12130 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 12131 mReadPendingIntent, participants, mLatestTimestamp); 12132 } 12133 } 12134 } 12135 12136 /** 12137 * <p>Helper class to add Android TV extensions to notifications. To create a notification 12138 * with a TV extension: 12139 * 12140 * <ol> 12141 * <li>Create an {@link Notification.Builder}, setting any desired properties. 12142 * <li>Create a {@link TvExtender}. 12143 * <li>Set TV-specific properties using the {@code set} methods of 12144 * {@link TvExtender}. 12145 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 12146 * to apply the extension to a notification. 12147 * </ol> 12148 * 12149 * <pre class="prettyprint"> 12150 * Notification notification = new Notification.Builder(context) 12151 * ... 12152 * .extend(new TvExtender() 12153 * .set*(...)) 12154 * .build(); 12155 * </pre> 12156 * 12157 * <p>TV extensions can be accessed on an existing notification by using the 12158 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 12159 * to access values. 12160 * 12161 * @hide 12162 */ 12163 @SystemApi 12164 public static final class TvExtender implements Extender { 12165 private static final String TAG = "TvExtender"; 12166 12167 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 12168 private static final String EXTRA_FLAGS = "flags"; 12169 static final String EXTRA_CONTENT_INTENT = "content_intent"; 12170 static final String EXTRA_DELETE_INTENT = "delete_intent"; 12171 private static final String EXTRA_CHANNEL_ID = "channel_id"; 12172 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 12173 12174 // Flags bitwise-ored to mFlags 12175 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 12176 12177 private int mFlags; 12178 private String mChannelId; 12179 private PendingIntent mContentIntent; 12180 private PendingIntent mDeleteIntent; 12181 private boolean mSuppressShowOverApps; 12182 12183 /** 12184 * Create a {@link TvExtender} with default options. 12185 */ TvExtender()12186 public TvExtender() { 12187 mFlags = FLAG_AVAILABLE_ON_TV; 12188 } 12189 12190 /** 12191 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 12192 * 12193 * @param notif The notification from which to copy options. 12194 */ TvExtender(Notification notif)12195 public TvExtender(Notification notif) { 12196 Bundle bundle = notif.extras == null ? 12197 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 12198 if (bundle != null) { 12199 mFlags = bundle.getInt(EXTRA_FLAGS); 12200 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 12201 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 12202 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class); 12203 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class); 12204 } 12205 } 12206 12207 /** 12208 * Apply a TV extension to a notification that is being built. This is typically called by 12209 * the {@link Notification.Builder#extend(Notification.Extender)} 12210 * method of {@link Notification.Builder}. 12211 */ 12212 @Override extend(Notification.Builder builder)12213 public Notification.Builder extend(Notification.Builder builder) { 12214 Bundle bundle = new Bundle(); 12215 12216 bundle.putInt(EXTRA_FLAGS, mFlags); 12217 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 12218 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 12219 if (mContentIntent != null) { 12220 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 12221 } 12222 12223 if (mDeleteIntent != null) { 12224 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 12225 } 12226 12227 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 12228 return builder; 12229 } 12230 12231 /** 12232 * Returns true if this notification should be shown on TV. This method return true 12233 * if the notification was extended with a TvExtender. 12234 */ isAvailableOnTv()12235 public boolean isAvailableOnTv() { 12236 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 12237 } 12238 12239 /** 12240 * Specifies the channel the notification should be delivered on when shown on TV. 12241 * It can be different from the channel that the notification is delivered to when 12242 * posting on a non-TV device. 12243 */ setChannel(String channelId)12244 public TvExtender setChannel(String channelId) { 12245 mChannelId = channelId; 12246 return this; 12247 } 12248 12249 /** 12250 * Specifies the channel the notification should be delivered on when shown on TV. 12251 * It can be different from the channel that the notification is delivered to when 12252 * posting on a non-TV device. 12253 */ setChannelId(String channelId)12254 public TvExtender setChannelId(String channelId) { 12255 mChannelId = channelId; 12256 return this; 12257 } 12258 12259 /** @removed */ 12260 @Deprecated getChannel()12261 public String getChannel() { 12262 return mChannelId; 12263 } 12264 12265 /** 12266 * Returns the id of the channel this notification posts to on TV. 12267 */ getChannelId()12268 public String getChannelId() { 12269 return mChannelId; 12270 } 12271 12272 /** 12273 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 12274 * If provided, it is used instead of the content intent specified 12275 * at the level of Notification. 12276 */ setContentIntent(PendingIntent intent)12277 public TvExtender setContentIntent(PendingIntent intent) { 12278 mContentIntent = intent; 12279 return this; 12280 } 12281 12282 /** 12283 * Returns the TV-specific content intent. If this method returns null, the 12284 * main content intent on the notification should be used. 12285 * 12286 * @see {@link Notification#contentIntent} 12287 */ getContentIntent()12288 public PendingIntent getContentIntent() { 12289 return mContentIntent; 12290 } 12291 12292 /** 12293 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 12294 * by the user on TV. If provided, it is used instead of the delete intent specified 12295 * at the level of Notification. 12296 */ setDeleteIntent(PendingIntent intent)12297 public TvExtender setDeleteIntent(PendingIntent intent) { 12298 mDeleteIntent = intent; 12299 return this; 12300 } 12301 12302 /** 12303 * Returns the TV-specific delete intent. If this method returns null, the 12304 * main delete intent on the notification should be used. 12305 * 12306 * @see {@link Notification#deleteIntent} 12307 */ getDeleteIntent()12308 public PendingIntent getDeleteIntent() { 12309 return mDeleteIntent; 12310 } 12311 12312 /** 12313 * Specifies whether this notification should suppress showing a message over top of apps 12314 * outside of the launcher. 12315 */ setSuppressShowOverApps(boolean suppress)12316 public TvExtender setSuppressShowOverApps(boolean suppress) { 12317 mSuppressShowOverApps = suppress; 12318 return this; 12319 } 12320 12321 /** 12322 * Returns true if this notification should not show messages over top of apps 12323 * outside of the launcher. 12324 */ getSuppressShowOverApps()12325 public boolean getSuppressShowOverApps() { 12326 return mSuppressShowOverApps; 12327 } 12328 } 12329 12330 /** 12331 * Get an array of Parcelable objects from a parcelable array bundle field. 12332 * Update the bundle to have a typed array so fetches in the future don't need 12333 * to do an array copy. 12334 */ 12335 @Nullable getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)12336 private static <T extends Parcelable> T[] getParcelableArrayFromBundle( 12337 Bundle bundle, String key, Class<T> itemClass) { 12338 final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class); 12339 final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); 12340 if (arrayClass.isInstance(array) || array == null) { 12341 return (T[]) array; 12342 } 12343 final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); 12344 for (int i = 0; i < array.length; i++) { 12345 typedArray[i] = (T) array[i]; 12346 } 12347 bundle.putParcelableArray(key, typedArray); 12348 return typedArray; 12349 } 12350 12351 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)12352 public BuilderRemoteViews(Parcel parcel) { 12353 super(parcel); 12354 } 12355 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)12356 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 12357 super(appInfo, layoutId); 12358 } 12359 12360 @Override clone()12361 public BuilderRemoteViews clone() { 12362 Parcel p = Parcel.obtain(); 12363 writeToParcel(p, 0); 12364 p.setDataPosition(0); 12365 BuilderRemoteViews brv = new BuilderRemoteViews(p); 12366 p.recycle(); 12367 return brv; 12368 } 12369 12370 /** 12371 * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden. 12372 * 12373 * @see RemoteViews#shouldUseStaticFilter() 12374 */ 12375 @Override shouldUseStaticFilter()12376 protected boolean shouldUseStaticFilter() { 12377 return true; 12378 } 12379 } 12380 12381 /** 12382 * A result object where information about the template that was created is saved. 12383 */ 12384 private static class TemplateBindResult { 12385 boolean mRightIconVisible; 12386 float mRightIconWidthDp; 12387 float mRightIconHeightDp; 12388 12389 /** 12390 * The margin end that needs to be added to the heading so that it won't overlap 12391 * with the large icon. This value includes the space required to accommodate the large 12392 * icon, but should be added to the space needed to accommodate the expander. This does 12393 * not include the 16dp content margin that all notification views must have. 12394 */ 12395 public final MarginSet mHeadingExtraMarginSet = new MarginSet(); 12396 12397 /** 12398 * The margin end that needs to be added to the heading so that it won't overlap 12399 * with the large icon. This value includes the space required to accommodate the large 12400 * icon as well as the expander. This does not include the 16dp content margin that all 12401 * notification views must have. 12402 */ 12403 public final MarginSet mHeadingFullMarginSet = new MarginSet(); 12404 12405 /** 12406 * The margin end that needs to be added to the title text of the big state 12407 * so that it won't overlap with the large icon, but assuming the text can run under 12408 * the expander when that icon is not visible. 12409 */ 12410 public final MarginSet mTitleMarginSet = new MarginSet(); 12411 setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12412 public void setRightIconState(boolean visible, float widthDp, float heightDp, 12413 float marginEndDpIfVisible, float expanderSizeDp) { 12414 mRightIconVisible = visible; 12415 mRightIconWidthDp = widthDp; 12416 mRightIconHeightDp = heightDp; 12417 mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible); 12418 mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp); 12419 mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp); 12420 } 12421 12422 /** 12423 * This contains the end margins for a view when the right icon is visible or not. These 12424 * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the 12425 * left_icon and adjust the margins, and to undo that change as well. 12426 */ 12427 private class MarginSet { 12428 private float mValueIfGone; 12429 private float mValueIfVisible; 12430 setValues(float valueIfGone, float valueIfVisible)12431 public void setValues(float valueIfGone, float valueIfVisible) { 12432 mValueIfGone = valueIfGone; 12433 mValueIfVisible = valueIfVisible; 12434 } 12435 applyToView(@onNull RemoteViews views, @IdRes int viewId)12436 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) { 12437 applyToView(views, viewId, 0); 12438 } 12439 applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12440 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId, 12441 float extraMarginDp) { 12442 final float marginEndDp = getDpValue() + extraMarginDp; 12443 if (viewId == R.id.notification_header) { 12444 views.setFloat(R.id.notification_header, 12445 "setTopLineExtraMarginEndDp", marginEndDp); 12446 } else if (viewId == R.id.text || viewId == R.id.big_text) { 12447 if (mValueIfGone != 0) { 12448 throw new RuntimeException("Programming error: `text` and `big_text` use " 12449 + "ImageFloatingTextView which can either show a margin or not; " 12450 + "thus mValueIfGone must be 0, but it was " + mValueIfGone); 12451 } 12452 // Note that the caller must set "setNumIndentLines" to a positive int in order 12453 // for this margin to do anything at all. 12454 views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible); 12455 views.setBoolean(viewId, "setHasImage", mRightIconVisible); 12456 // Apply just the *extra* margin as the view layout margin; this will be 12457 // unchanged depending on the visibility of the image, but it means that the 12458 // extra margin applies to *every* line of text instead of just indented lines. 12459 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12460 extraMarginDp, TypedValue.COMPLEX_UNIT_DIP); 12461 } else { 12462 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12463 marginEndDp, TypedValue.COMPLEX_UNIT_DIP); 12464 } 12465 if (mRightIconVisible) { 12466 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, 12467 TypedValue.createComplexDimension( 12468 mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12469 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone, 12470 TypedValue.createComplexDimension( 12471 mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12472 } 12473 } 12474 getDpValue()12475 public float getDpValue() { 12476 return mRightIconVisible ? mValueIfVisible : mValueIfGone; 12477 } 12478 } 12479 } 12480 12481 private static class StandardTemplateParams { 12482 /** 12483 * Notifications will be minimally decorated with ONLY an icon and expander: 12484 * <li>A large icon is never shown. 12485 * <li>A progress bar is never shown. 12486 * <li>The expanded and heads up states do not show actions, even if provided. 12487 */ 12488 public static final int DECORATION_MINIMAL = 1; 12489 12490 /** 12491 * Notifications will be partially decorated with AT LEAST an icon and expander: 12492 * <li>A large icon is shown if provided. 12493 * <li>A progress bar is shown if provided and enough space remains below the content. 12494 * <li>Actions are shown in the expanded and heads up states. 12495 */ 12496 public static final int DECORATION_PARTIAL = 2; 12497 12498 public static int VIEW_TYPE_UNSPECIFIED = 0; 12499 public static int VIEW_TYPE_NORMAL = 1; 12500 public static int VIEW_TYPE_BIG = 2; 12501 public static int VIEW_TYPE_HEADS_UP = 3; 12502 public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state 12503 public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version 12504 public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group 12505 12506 int mViewType = VIEW_TYPE_UNSPECIFIED; 12507 boolean mHeaderless; 12508 boolean mHideAppName; 12509 boolean mHideTitle; 12510 boolean mHideSubText; 12511 boolean mHideTime; 12512 boolean mHideActions; 12513 boolean mHideProgress; 12514 boolean mHideSnoozeButton; 12515 boolean mHideLeftIcon; 12516 boolean mHideRightIcon; 12517 Icon mPromotedPicture; 12518 boolean mCallStyleActions; 12519 boolean mAllowTextWithProgress; 12520 int mTitleViewId; 12521 int mTextViewId; 12522 @Nullable CharSequence mTitle; 12523 @Nullable CharSequence mText; 12524 @Nullable CharSequence mHeaderTextSecondary; 12525 @Nullable CharSequence mSubText; 12526 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12527 boolean allowColorization = true; 12528 boolean mHighlightExpander = false; 12529 reset()12530 final StandardTemplateParams reset() { 12531 mViewType = VIEW_TYPE_UNSPECIFIED; 12532 mHeaderless = false; 12533 mHideAppName = false; 12534 mHideTitle = false; 12535 mHideSubText = false; 12536 mHideTime = false; 12537 mHideActions = false; 12538 mHideProgress = false; 12539 mHideSnoozeButton = false; 12540 mHideLeftIcon = false; 12541 mHideRightIcon = false; 12542 mPromotedPicture = null; 12543 mCallStyleActions = false; 12544 mAllowTextWithProgress = false; 12545 mTitleViewId = R.id.title; 12546 mTextViewId = R.id.text; 12547 mTitle = null; 12548 mText = null; 12549 mSubText = null; 12550 mHeaderTextSecondary = null; 12551 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12552 allowColorization = true; 12553 mHighlightExpander = false; 12554 return this; 12555 } 12556 hasTitle()12557 final boolean hasTitle() { 12558 return !TextUtils.isEmpty(mTitle) && !mHideTitle; 12559 } 12560 viewType(int viewType)12561 final StandardTemplateParams viewType(int viewType) { 12562 mViewType = viewType; 12563 return this; 12564 } 12565 headerless(boolean headerless)12566 public StandardTemplateParams headerless(boolean headerless) { 12567 mHeaderless = headerless; 12568 return this; 12569 } 12570 hideAppName(boolean hideAppName)12571 public StandardTemplateParams hideAppName(boolean hideAppName) { 12572 mHideAppName = hideAppName; 12573 return this; 12574 } 12575 hideSubText(boolean hideSubText)12576 public StandardTemplateParams hideSubText(boolean hideSubText) { 12577 mHideSubText = hideSubText; 12578 return this; 12579 } 12580 hideTime(boolean hideTime)12581 public StandardTemplateParams hideTime(boolean hideTime) { 12582 mHideTime = hideTime; 12583 return this; 12584 } 12585 hideActions(boolean hideActions)12586 final StandardTemplateParams hideActions(boolean hideActions) { 12587 this.mHideActions = hideActions; 12588 return this; 12589 } 12590 hideProgress(boolean hideProgress)12591 final StandardTemplateParams hideProgress(boolean hideProgress) { 12592 this.mHideProgress = hideProgress; 12593 return this; 12594 } 12595 hideTitle(boolean hideTitle)12596 final StandardTemplateParams hideTitle(boolean hideTitle) { 12597 this.mHideTitle = hideTitle; 12598 return this; 12599 } 12600 callStyleActions(boolean callStyleActions)12601 final StandardTemplateParams callStyleActions(boolean callStyleActions) { 12602 this.mCallStyleActions = callStyleActions; 12603 return this; 12604 } 12605 allowTextWithProgress(boolean allowTextWithProgress)12606 final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) { 12607 this.mAllowTextWithProgress = allowTextWithProgress; 12608 return this; 12609 } 12610 hideSnoozeButton(boolean hideSnoozeButton)12611 final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) { 12612 this.mHideSnoozeButton = hideSnoozeButton; 12613 return this; 12614 } 12615 promotedPicture(Icon promotedPicture)12616 final StandardTemplateParams promotedPicture(Icon promotedPicture) { 12617 this.mPromotedPicture = promotedPicture; 12618 return this; 12619 } 12620 titleViewId(int titleViewId)12621 public StandardTemplateParams titleViewId(int titleViewId) { 12622 mTitleViewId = titleViewId; 12623 return this; 12624 } 12625 textViewId(int textViewId)12626 public StandardTemplateParams textViewId(int textViewId) { 12627 mTextViewId = textViewId; 12628 return this; 12629 } 12630 title(@ullable CharSequence title)12631 final StandardTemplateParams title(@Nullable CharSequence title) { 12632 this.mTitle = title; 12633 return this; 12634 } 12635 text(@ullable CharSequence text)12636 final StandardTemplateParams text(@Nullable CharSequence text) { 12637 this.mText = text; 12638 return this; 12639 } 12640 summaryText(@ullable CharSequence text)12641 final StandardTemplateParams summaryText(@Nullable CharSequence text) { 12642 this.mSubText = text; 12643 return this; 12644 } 12645 headerTextSecondary(@ullable CharSequence text)12646 final StandardTemplateParams headerTextSecondary(@Nullable CharSequence text) { 12647 this.mHeaderTextSecondary = text; 12648 return this; 12649 } 12650 12651 hideLeftIcon(boolean hideLeftIcon)12652 final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) { 12653 this.mHideLeftIcon = hideLeftIcon; 12654 return this; 12655 } 12656 hideRightIcon(boolean hideRightIcon)12657 final StandardTemplateParams hideRightIcon(boolean hideRightIcon) { 12658 this.mHideRightIcon = hideRightIcon; 12659 return this; 12660 } 12661 disallowColorization()12662 final StandardTemplateParams disallowColorization() { 12663 this.allowColorization = false; 12664 return this; 12665 } 12666 highlightExpander(boolean highlight)12667 final StandardTemplateParams highlightExpander(boolean highlight) { 12668 this.mHighlightExpander = highlight; 12669 return this; 12670 } 12671 fillTextsFrom(Builder b)12672 final StandardTemplateParams fillTextsFrom(Builder b) { 12673 Bundle extras = b.mN.extras; 12674 this.mTitle = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 12675 this.mText = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 12676 this.mSubText = extras.getCharSequence(EXTRA_SUB_TEXT); 12677 return this; 12678 } 12679 12680 /** 12681 * Set the maximum lines of remote input history lines allowed. 12682 * @param maxRemoteInputHistory The number of lines. 12683 * @return The builder for method chaining. 12684 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)12685 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 12686 this.maxRemoteInputHistory = maxRemoteInputHistory; 12687 return this; 12688 } 12689 decorationType(int decorationType)12690 public StandardTemplateParams decorationType(int decorationType) { 12691 hideTitle(true); 12692 // Minimally decorated custom views do not show certain pieces of chrome that have 12693 // always been shown when using DecoratedCustomViewStyle. 12694 boolean hideOtherFields = decorationType <= DECORATION_MINIMAL; 12695 hideLeftIcon(false); // The left icon decoration is better than showing nothing. 12696 hideRightIcon(hideOtherFields); 12697 hideProgress(hideOtherFields); 12698 hideActions(hideOtherFields); 12699 return this; 12700 } 12701 } 12702 12703 /** 12704 * A utility which stores and calculates the palette of colors used to color notifications. 12705 * @hide 12706 */ 12707 @VisibleForTesting 12708 public static class Colors { 12709 private int mPaletteIsForRawColor = COLOR_INVALID; 12710 private boolean mPaletteIsForColorized = false; 12711 private boolean mPaletteIsForNightMode = false; 12712 // The following colors are the palette 12713 private int mBackgroundColor = COLOR_INVALID; 12714 private int mProtectionColor = COLOR_INVALID; 12715 private int mPrimaryTextColor = COLOR_INVALID; 12716 private int mSecondaryTextColor = COLOR_INVALID; 12717 private int mPrimaryAccentColor = COLOR_INVALID; 12718 private int mSecondaryAccentColor = COLOR_INVALID; 12719 private int mTertiaryAccentColor = COLOR_INVALID; 12720 private int mOnAccentTextColor = COLOR_INVALID; 12721 private int mErrorColor = COLOR_INVALID; 12722 private int mContrastColor = COLOR_INVALID; 12723 private int mRippleAlpha = 0x33; 12724 12725 /** 12726 * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which 12727 * returns null when the context is a mock with no theme. 12728 * 12729 * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper 12730 * instances can allocate as much as 5MB of memory, so its important to call this method 12731 * only when necessary, getting as many attributes as possible from each call. 12732 * 12733 * @see Resources.Theme#obtainStyledAttributes(int[]) 12734 */ 12735 @Nullable obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12736 private static TypedArray obtainDayNightAttributes(@NonNull Context ctx, 12737 @NonNull @StyleableRes int[] attrs) { 12738 // when testing, the mock context may have no theme 12739 if (ctx.getTheme() == null) { 12740 return null; 12741 } 12742 Resources.Theme theme = new ContextThemeWrapper(ctx, 12743 R.style.Theme_DeviceDefault_DayNight).getTheme(); 12744 return theme.obtainStyledAttributes(attrs); 12745 } 12746 12747 /** A null-safe wrapper of TypedArray.getColor because mocks return null */ getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12748 private static @ColorInt int getColor(@Nullable TypedArray ta, int index, 12749 @ColorInt int defValue) { 12750 return ta == null ? defValue : ta.getColor(index, defValue); 12751 } 12752 12753 /** 12754 * Resolve the palette. If the inputs have not changed, this will be a no-op. 12755 * This does not handle invalidating the resolved colors when the context itself changes, 12756 * because that case does not happen in the current notification inflation pipeline; we will 12757 * recreate a new builder (and thus a new palette) when reinflating notifications for a new 12758 * theme (admittedly, we do the same for night mode, but that's easy to check). 12759 * 12760 * @param ctx the builder context. 12761 * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha. 12762 * @param isColorized whether the notification is colorized. 12763 * @param nightMode whether the UI is in night mode. 12764 */ resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12765 public void resolvePalette(Context ctx, int rawColor, 12766 boolean isColorized, boolean nightMode) { 12767 if (mPaletteIsForRawColor == rawColor 12768 && mPaletteIsForColorized == isColorized 12769 && mPaletteIsForNightMode == nightMode) { 12770 return; 12771 } 12772 mPaletteIsForRawColor = rawColor; 12773 mPaletteIsForColorized = isColorized; 12774 mPaletteIsForNightMode = nightMode; 12775 12776 if (isColorized) { 12777 if (rawColor == COLOR_DEFAULT) { 12778 int[] attrs = {R.attr.colorAccentSecondary}; 12779 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12780 mBackgroundColor = getColor(ta, 0, Color.WHITE); 12781 } 12782 } else { 12783 mBackgroundColor = rawColor; 12784 } 12785 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12786 ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode), 12787 mBackgroundColor, 4.5); 12788 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12789 ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode), 12790 mBackgroundColor, 4.5); 12791 mContrastColor = mPrimaryTextColor; 12792 mPrimaryAccentColor = mPrimaryTextColor; 12793 mSecondaryAccentColor = mSecondaryTextColor; 12794 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); 12795 mOnAccentTextColor = mBackgroundColor; 12796 mErrorColor = mPrimaryTextColor; 12797 mRippleAlpha = 0x33; 12798 } else { 12799 int[] attrs = { 12800 R.attr.colorSurface, 12801 R.attr.textColorPrimary, 12802 R.attr.textColorSecondary, 12803 R.attr.colorAccent, 12804 R.attr.colorAccentSecondary, 12805 R.attr.colorAccentTertiary, 12806 R.attr.textColorOnAccent, 12807 R.attr.colorError, 12808 R.attr.colorControlHighlight 12809 }; 12810 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12811 mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE); 12812 mPrimaryTextColor = getColor(ta, 1, COLOR_INVALID); 12813 mSecondaryTextColor = getColor(ta, 2, COLOR_INVALID); 12814 mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID); 12815 mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID); 12816 mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID); 12817 mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID); 12818 mErrorColor = getColor(ta, 7, COLOR_INVALID); 12819 mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff)); 12820 } 12821 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor, 12822 mBackgroundColor, nightMode); 12823 12824 // make sure every color has a valid value 12825 if (mPrimaryTextColor == COLOR_INVALID) { 12826 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor( 12827 ctx, mBackgroundColor, nightMode); 12828 } 12829 if (mSecondaryTextColor == COLOR_INVALID) { 12830 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor( 12831 ctx, mBackgroundColor, nightMode); 12832 } 12833 if (mPrimaryAccentColor == COLOR_INVALID) { 12834 mPrimaryAccentColor = mContrastColor; 12835 } 12836 if (mSecondaryAccentColor == COLOR_INVALID) { 12837 mSecondaryAccentColor = mContrastColor; 12838 } 12839 if (mTertiaryAccentColor == COLOR_INVALID) { 12840 mTertiaryAccentColor = mContrastColor; 12841 } 12842 if (mOnAccentTextColor == COLOR_INVALID) { 12843 mOnAccentTextColor = ColorUtils.setAlphaComponent( 12844 ContrastColorUtil.resolvePrimaryColor( 12845 ctx, mTertiaryAccentColor, nightMode), 0xFF); 12846 } 12847 if (mErrorColor == COLOR_INVALID) { 12848 mErrorColor = mPrimaryTextColor; 12849 } 12850 } 12851 // make sure every color has a valid value 12852 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f); 12853 } 12854 12855 /** calculates the contrast color for the non-colorized notifications */ calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12856 private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor, 12857 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) { 12858 int color; 12859 if (rawColor == COLOR_DEFAULT) { 12860 color = accentColor; 12861 if (color == COLOR_INVALID) { 12862 color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode); 12863 } 12864 } else { 12865 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor, 12866 nightMode); 12867 } 12868 return flattenAlpha(color, backgroundColor); 12869 } 12870 12871 /** remove any alpha by manually blending it with the given background. */ flattenAlpha(@olorInt int color, @ColorInt int background)12872 private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) { 12873 return Color.alpha(color) == 0xff ? color 12874 : ContrastColorUtil.compositeColors(color, background); 12875 } 12876 12877 /** @return the notification's background color */ getBackgroundColor()12878 public @ColorInt int getBackgroundColor() { 12879 return mBackgroundColor; 12880 } 12881 12882 /** 12883 * @return the "surface protection" color from the theme, 12884 * or a variant of the normal background color when colorized. 12885 */ getProtectionColor()12886 public @ColorInt int getProtectionColor() { 12887 return mProtectionColor; 12888 } 12889 12890 /** @return the color for the most prominent text */ getPrimaryTextColor()12891 public @ColorInt int getPrimaryTextColor() { 12892 return mPrimaryTextColor; 12893 } 12894 12895 /** @return the color for less prominent text */ getSecondaryTextColor()12896 public @ColorInt int getSecondaryTextColor() { 12897 return mSecondaryTextColor; 12898 } 12899 12900 /** @return the theme's accent color for colored UI elements. */ getPrimaryAccentColor()12901 public @ColorInt int getPrimaryAccentColor() { 12902 return mPrimaryAccentColor; 12903 } 12904 12905 /** @return the theme's secondary accent color for colored UI elements. */ getSecondaryAccentColor()12906 public @ColorInt int getSecondaryAccentColor() { 12907 return mSecondaryAccentColor; 12908 } 12909 12910 /** @return the theme's tertiary accent color for colored UI elements. */ getTertiaryAccentColor()12911 public @ColorInt int getTertiaryAccentColor() { 12912 return mTertiaryAccentColor; 12913 } 12914 12915 /** @return the theme's text color to be used on the tertiary accent color. */ getOnAccentTextColor()12916 public @ColorInt int getOnAccentTextColor() { 12917 return mOnAccentTextColor; 12918 } 12919 12920 /** 12921 * @return the contrast-adjusted version of the color provided by the app, or the 12922 * primary text color when colorized. 12923 */ getContrastColor()12924 public @ColorInt int getContrastColor() { 12925 return mContrastColor; 12926 } 12927 12928 /** @return the theme's error color, or the primary text color when colorized. */ getErrorColor()12929 public @ColorInt int getErrorColor() { 12930 return mErrorColor; 12931 } 12932 12933 /** @return the alpha component of the current theme's control highlight color. */ getRippleAlpha()12934 public int getRippleAlpha() { 12935 return mRippleAlpha; 12936 } 12937 } 12938 } 12939