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 private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( 707 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 708 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 709 MessagingStyle.class, CallStyle.class); 710 711 /** @hide */ 712 @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, 713 FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, 714 FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, 715 FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE}) 716 @Retention(RetentionPolicy.SOURCE) 717 public @interface NotificationFlags{}; 718 719 public int flags; 720 721 /** @hide */ 722 @IntDef(prefix = { "PRIORITY_" }, value = { 723 PRIORITY_DEFAULT, 724 PRIORITY_LOW, 725 PRIORITY_MIN, 726 PRIORITY_HIGH, 727 PRIORITY_MAX 728 }) 729 @Retention(RetentionPolicy.SOURCE) 730 public @interface Priority {} 731 732 /** 733 * Default notification {@link #priority}. If your application does not prioritize its own 734 * notifications, use this value for all notifications. 735 * 736 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. 737 */ 738 @Deprecated 739 public static final int PRIORITY_DEFAULT = 0; 740 741 /** 742 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 743 * items smaller, or at a different position in the list, compared with your app's 744 * {@link #PRIORITY_DEFAULT} items. 745 * 746 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. 747 */ 748 @Deprecated 749 public static final int PRIORITY_LOW = -1; 750 751 /** 752 * Lowest {@link #priority}; these items might not be shown to the user except under special 753 * circumstances, such as detailed notification logs. 754 * 755 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. 756 */ 757 @Deprecated 758 public static final int PRIORITY_MIN = -2; 759 760 /** 761 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 762 * show these items larger, or at a different position in notification lists, compared with 763 * your app's {@link #PRIORITY_DEFAULT} items. 764 * 765 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 766 */ 767 @Deprecated 768 public static final int PRIORITY_HIGH = 1; 769 770 /** 771 * Highest {@link #priority}, for your application's most important items that require the 772 * user's prompt attention or input. 773 * 774 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 775 */ 776 @Deprecated 777 public static final int PRIORITY_MAX = 2; 778 779 /** 780 * Relative priority for this notification. 781 * 782 * Priority is an indication of how much of the user's valuable attention should be consumed by 783 * this notification. Low-priority notifications may be hidden from the user in certain 784 * situations, while the user might be interrupted for a higher-priority notification. The 785 * system will make a determination about how to interpret this priority when presenting 786 * the notification. 787 * 788 * <p> 789 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 790 * as a heads-up notification. 791 * </p> 792 * 793 * @deprecated use {@link NotificationChannel#getImportance()} instead. 794 */ 795 @Priority 796 @Deprecated 797 public int priority; 798 799 /** 800 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 801 * to be applied by the standard Style templates when presenting this notification. 802 * 803 * The current template design constructs a colorful header image by overlaying the 804 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 805 * ignored. 806 */ 807 @ColorInt 808 public int color = COLOR_DEFAULT; 809 810 /** 811 * Special value of {@link #color} telling the system not to decorate this notification with 812 * any special color but instead use default colors when presenting this notification. 813 */ 814 @ColorInt 815 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 816 817 /** 818 * Special value of {@link #color} used as a place holder for an invalid color. 819 * @hide 820 */ 821 @ColorInt 822 public static final int COLOR_INVALID = 1; 823 824 /** 825 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 826 * the notification's presence and contents in untrusted situations (namely, on the secure 827 * lockscreen). 828 * 829 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 830 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 831 * shown in all situations, but the contents are only available if the device is unlocked for 832 * the appropriate user. 833 * 834 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 835 * can be read even in an "insecure" context (that is, above a secure lockscreen). 836 * To modify the public version of this notification—for example, to redact some portions—see 837 * {@link Builder#setPublicVersion(Notification)}. 838 * 839 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 840 * and ticker until the user has bypassed the lockscreen. 841 */ 842 public @Visibility int visibility; 843 844 /** @hide */ 845 @IntDef(prefix = { "VISIBILITY_" }, value = { 846 VISIBILITY_PUBLIC, 847 VISIBILITY_PRIVATE, 848 VISIBILITY_SECRET, 849 }) 850 @Retention(RetentionPolicy.SOURCE) 851 public @interface Visibility {} 852 853 /** 854 * Notification visibility: Show this notification in its entirety on all lockscreens. 855 * 856 * {@see #visibility} 857 */ 858 public static final int VISIBILITY_PUBLIC = 1; 859 860 /** 861 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 862 * private information on secure lockscreens. 863 * 864 * {@see #visibility} 865 */ 866 public static final int VISIBILITY_PRIVATE = 0; 867 868 /** 869 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen. 870 * 871 * {@see #visibility} 872 */ 873 public static final int VISIBILITY_SECRET = -1; 874 875 /** 876 * @hide 877 */ 878 @IntDef(prefix = "VISIBILITY_", value = { 879 VISIBILITY_PUBLIC, 880 VISIBILITY_PRIVATE, 881 VISIBILITY_SECRET, 882 NotificationManager.VISIBILITY_NO_OVERRIDE 883 }) 884 public @interface NotificationVisibilityOverride{}; 885 886 /** 887 * Notification category: incoming call (voice or video) or similar synchronous communication request. 888 */ 889 public static final String CATEGORY_CALL = "call"; 890 891 /** 892 * Notification category: map turn-by-turn navigation. 893 */ 894 public static final String CATEGORY_NAVIGATION = "navigation"; 895 896 /** 897 * Notification category: incoming direct message (SMS, instant message, etc.). 898 */ 899 public static final String CATEGORY_MESSAGE = "msg"; 900 901 /** 902 * Notification category: asynchronous bulk message (email). 903 */ 904 public static final String CATEGORY_EMAIL = "email"; 905 906 /** 907 * Notification category: calendar event. 908 */ 909 public static final String CATEGORY_EVENT = "event"; 910 911 /** 912 * Notification category: promotion or advertisement. 913 */ 914 public static final String CATEGORY_PROMO = "promo"; 915 916 /** 917 * Notification category: alarm or timer. 918 */ 919 public static final String CATEGORY_ALARM = "alarm"; 920 921 /** 922 * Notification category: progress of a long-running background operation. 923 */ 924 public static final String CATEGORY_PROGRESS = "progress"; 925 926 /** 927 * Notification category: social network or sharing update. 928 */ 929 public static final String CATEGORY_SOCIAL = "social"; 930 931 /** 932 * Notification category: error in background operation or authentication status. 933 */ 934 public static final String CATEGORY_ERROR = "err"; 935 936 /** 937 * Notification category: media transport control for playback. 938 */ 939 public static final String CATEGORY_TRANSPORT = "transport"; 940 941 /** 942 * Notification category: system or device status update. Reserved for system use. 943 */ 944 public static final String CATEGORY_SYSTEM = "sys"; 945 946 /** 947 * Notification category: indication of running background service. 948 */ 949 public static final String CATEGORY_SERVICE = "service"; 950 951 /** 952 * Notification category: a specific, timely recommendation for a single thing. 953 * For example, a news app might want to recommend a news story it believes the user will 954 * want to read next. 955 */ 956 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 957 958 /** 959 * Notification category: ongoing information about device or contextual status. 960 */ 961 public static final String CATEGORY_STATUS = "status"; 962 963 /** 964 * Notification category: user-scheduled reminder. 965 */ 966 public static final String CATEGORY_REMINDER = "reminder"; 967 968 /** 969 * Notification category: extreme car emergencies. 970 * @hide 971 */ 972 @SystemApi 973 public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; 974 975 /** 976 * Notification category: car warnings. 977 * @hide 978 */ 979 @SystemApi 980 public static final String CATEGORY_CAR_WARNING = "car_warning"; 981 982 /** 983 * Notification category: general car system information. 984 * @hide 985 */ 986 @SystemApi 987 public static final String CATEGORY_CAR_INFORMATION = "car_information"; 988 989 /** 990 * Notification category: tracking a user's workout. 991 */ 992 public static final String CATEGORY_WORKOUT = "workout"; 993 994 /** 995 * Notification category: temporarily sharing location. 996 */ 997 public static final String CATEGORY_LOCATION_SHARING = "location_sharing"; 998 999 /** 1000 * Notification category: running stopwatch. 1001 */ 1002 public static final String CATEGORY_STOPWATCH = "stopwatch"; 1003 1004 /** 1005 * Notification category: missed call. 1006 */ 1007 public static final String CATEGORY_MISSED_CALL = "missed_call"; 1008 1009 /** 1010 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 1011 * that best describes this Notification. May be used by the system for ranking and filtering. 1012 */ 1013 public String category; 1014 1015 @UnsupportedAppUsage 1016 private String mGroupKey; 1017 1018 /** 1019 * Get the key used to group this notification into a cluster or stack 1020 * with other notifications on devices which support such rendering. 1021 */ getGroup()1022 public String getGroup() { 1023 return mGroupKey; 1024 } 1025 1026 private String mSortKey; 1027 1028 /** 1029 * Get a sort key that orders this notification among other notifications from the 1030 * same package. This can be useful if an external sort was already applied and an app 1031 * would like to preserve this. Notifications will be sorted lexicographically using this 1032 * value, although providing different priorities in addition to providing sort key may 1033 * cause this value to be ignored. 1034 * 1035 * <p>This sort key can also be used to order members of a notification group. See 1036 * {@link Builder#setGroup}. 1037 * 1038 * @see String#compareTo(String) 1039 */ getSortKey()1040 public String getSortKey() { 1041 return mSortKey; 1042 } 1043 1044 /** 1045 * Additional semantic data to be carried around with this Notification. 1046 * <p> 1047 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 1048 * APIs, and are intended to be used by 1049 * {@link android.service.notification.NotificationListenerService} implementations to extract 1050 * detailed information from notification objects. 1051 */ 1052 public Bundle extras = new Bundle(); 1053 1054 /** 1055 * All pending intents in the notification as the system needs to be able to access them but 1056 * touching the extras bundle in the system process is not safe because the bundle may contain 1057 * custom parcelable objects. 1058 * 1059 * @hide 1060 */ 1061 @UnsupportedAppUsage 1062 public ArraySet<PendingIntent> allPendingIntents; 1063 1064 /** 1065 * Token identifying the notification that is applying doze/bgcheck allowlisting to the 1066 * pending intents inside of it, so only those will get the behavior. 1067 * 1068 * @hide 1069 */ 1070 private IBinder mAllowlistToken; 1071 1072 /** 1073 * Must be set by a process to start associating tokens with Notification objects 1074 * coming in to it. This is set by NotificationManagerService. 1075 * 1076 * @hide 1077 */ 1078 static public IBinder processAllowlistToken; 1079 1080 /** 1081 * {@link #extras} key: this is the title of the notification, 1082 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 1083 */ 1084 public static final String EXTRA_TITLE = "android.title"; 1085 1086 /** 1087 * {@link #extras} key: this is the title of the notification when shown in expanded form, 1088 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 1089 */ 1090 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 1091 1092 /** 1093 * {@link #extras} key: this is the main text payload, as supplied to 1094 * {@link Builder#setContentText(CharSequence)}. 1095 */ 1096 public static final String EXTRA_TEXT = "android.text"; 1097 1098 /** 1099 * {@link #extras} key: this is a third line of text, as supplied to 1100 * {@link Builder#setSubText(CharSequence)}. 1101 */ 1102 public static final String EXTRA_SUB_TEXT = "android.subText"; 1103 1104 /** 1105 * {@link #extras} key: this is the remote input history, as supplied to 1106 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1107 * 1108 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 1109 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 1110 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 1111 * notifications once the other party has responded). 1112 * 1113 * The extra with this key is of type CharSequence[] and contains the most recent entry at 1114 * the 0 index, the second most recent at the 1 index, etc. 1115 * 1116 * @see Builder#setRemoteInputHistory(CharSequence[]) 1117 */ 1118 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 1119 1120 1121 /** 1122 * {@link #extras} key: this is a remote input history which can include media messages 1123 * in addition to text, as supplied to 1124 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or 1125 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1126 * 1127 * SystemUI can populate this through 1128 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs 1129 * that have been sent through a {@link RemoteInput} of this Notification. These items can 1130 * represent either media content (specified by a URI and a MIME type) or a text message 1131 * (described by a CharSequence). 1132 * 1133 * To maintain compatibility, this can also be set by apps with 1134 * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a 1135 * {@link RemoteInputHistoryItem} for each of the provided text-only messages. 1136 * 1137 * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most 1138 * recent entry at the 0 index, the second most recent at the 1 index, etc. 1139 * 1140 * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) 1141 * @hide 1142 */ 1143 public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; 1144 1145 /** 1146 * {@link #extras} key: boolean as supplied to 1147 * {@link Builder#setShowRemoteInputSpinner(boolean)}. 1148 * 1149 * If set to true, then the view displaying the remote input history from 1150 * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner. 1151 * 1152 * @see Builder#setShowRemoteInputSpinner(boolean) 1153 * @hide 1154 */ 1155 public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner"; 1156 1157 /** 1158 * {@link #extras} key: boolean as supplied to 1159 * {@link Builder#setHideSmartReplies(boolean)}. 1160 * 1161 * If set to true, then any smart reply buttons will be hidden. 1162 * 1163 * @see Builder#setHideSmartReplies(boolean) 1164 * @hide 1165 */ 1166 public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies"; 1167 1168 /** 1169 * {@link #extras} key: this is a small piece of additional text as supplied to 1170 * {@link Builder#setContentInfo(CharSequence)}. 1171 */ 1172 public static final String EXTRA_INFO_TEXT = "android.infoText"; 1173 1174 /** 1175 * {@link #extras} key: this is a line of summary information intended to be shown 1176 * alongside expanded notifications, as supplied to (e.g.) 1177 * {@link BigTextStyle#setSummaryText(CharSequence)}. 1178 */ 1179 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 1180 1181 /** 1182 * {@link #extras} key: this is the longer text shown in the big form of a 1183 * {@link BigTextStyle} notification, as supplied to 1184 * {@link BigTextStyle#bigText(CharSequence)}. 1185 */ 1186 public static final String EXTRA_BIG_TEXT = "android.bigText"; 1187 1188 /** 1189 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 1190 * supplied to {@link Builder#setSmallIcon(int)}. 1191 * 1192 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources. 1193 */ 1194 @Deprecated 1195 public static final String EXTRA_SMALL_ICON = "android.icon"; 1196 1197 /** 1198 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 1199 * notification payload, as 1200 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 1201 * 1202 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources. 1203 */ 1204 @Deprecated 1205 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 1206 1207 /** 1208 * {@link #extras} key: this is a bitmap to be used instead of the one from 1209 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 1210 * shown in its expanded form, as supplied to 1211 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 1212 */ 1213 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 1214 1215 /** 1216 * {@link #extras} key: this is the progress value supplied to 1217 * {@link Builder#setProgress(int, int, boolean)}. 1218 */ 1219 public static final String EXTRA_PROGRESS = "android.progress"; 1220 1221 /** 1222 * {@link #extras} key: this is the maximum value supplied to 1223 * {@link Builder#setProgress(int, int, boolean)}. 1224 */ 1225 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 1226 1227 /** 1228 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 1229 * {@link Builder#setProgress(int, int, boolean)}. 1230 */ 1231 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 1232 1233 /** 1234 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 1235 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 1236 * {@link Builder#setUsesChronometer(boolean)}. 1237 */ 1238 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 1239 1240 /** 1241 * {@link #extras} key: whether the chronometer set on the notification should count down 1242 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 1243 * This extra is a boolean. The default is false. 1244 */ 1245 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 1246 1247 /** 1248 * {@link #extras} key: whether {@link #when} should be shown, 1249 * as supplied to {@link Builder#setShowWhen(boolean)}. 1250 */ 1251 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 1252 1253 /** 1254 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 1255 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 1256 */ 1257 public static final String EXTRA_PICTURE = "android.picture"; 1258 1259 /** 1260 * {@link #extras} key: this is an {@link Icon} of an image to be 1261 * shown in {@link BigPictureStyle} expanded notifications, supplied to 1262 * {@link BigPictureStyle#bigPicture(Icon)}. 1263 */ 1264 public static final String EXTRA_PICTURE_ICON = "android.pictureIcon"; 1265 1266 /** 1267 * {@link #extras} key: this is a content description of the big picture supplied from 1268 * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to 1269 * {@link BigPictureStyle#setContentDescription(CharSequence)}. 1270 */ 1271 public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = 1272 "android.pictureContentDescription"; 1273 1274 /** 1275 * {@link #extras} key: this is a boolean to indicate that the 1276 * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state 1277 * of a {@link BigPictureStyle} notification. This will replace a 1278 * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided. 1279 */ 1280 public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = 1281 "android.showBigPictureWhenCollapsed"; 1282 1283 /** 1284 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 1285 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 1286 */ 1287 public static final String EXTRA_TEXT_LINES = "android.textLines"; 1288 1289 /** 1290 * {@link #extras} key: A string representing the name of the specific 1291 * {@link android.app.Notification.Style} used to create this notification. 1292 */ 1293 public static final String EXTRA_TEMPLATE = "android.template"; 1294 1295 /** 1296 * {@link #extras} key: A String array containing the people that this notification relates to, 1297 * each of which was supplied to {@link Builder#addPerson(String)}. 1298 * 1299 * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} 1300 */ 1301 public static final String EXTRA_PEOPLE = "android.people"; 1302 1303 /** 1304 * {@link #extras} key: An arrayList of {@link Person} objects containing the people that 1305 * this notification relates to. 1306 */ 1307 public static final String EXTRA_PEOPLE_LIST = "android.people.list"; 1308 1309 /** 1310 * Allow certain system-generated notifications to appear before the device is provisioned. 1311 * Only available to notifications coming from the android package. 1312 * @hide 1313 */ 1314 @SystemApi 1315 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP) 1316 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 1317 1318 /** 1319 * {@link #extras} key: 1320 * flat {@link String} representation of a {@link android.content.ContentUris content URI} 1321 * pointing to an image that can be displayed in the background when the notification is 1322 * selected. Used on television platforms. The URI must point to an image stream suitable for 1323 * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1324 * BitmapFactory.decodeStream}; all other content types will be ignored. 1325 */ 1326 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 1327 1328 /** 1329 * {@link #extras} key: A 1330 * {@link android.media.session.MediaSession.Token} associated with a 1331 * {@link android.app.Notification.MediaStyle} notification. 1332 */ 1333 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 1334 1335 /** 1336 * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session 1337 * associated with a {@link Notification.MediaStyle} notification. This will show in the media 1338 * controls output switcher instead of the local device name. 1339 * @hide 1340 */ 1341 @TestApi 1342 public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; 1343 1344 /** 1345 * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output 1346 * switcher of the media controls for a {@link Notification.MediaStyle} notification. 1347 * @hide 1348 */ 1349 @TestApi 1350 public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; 1351 1352 /** 1353 * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the 1354 * media controls output switcher chip, associated with a {@link Notification.MediaStyle} 1355 * notification. This should launch an activity. 1356 * @hide 1357 */ 1358 @TestApi 1359 public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; 1360 1361 /** 1362 * {@link #extras} key: the indices of actions to be shown in the compact view, 1363 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 1364 */ 1365 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 1366 1367 /** 1368 * {@link #extras} key: the username to be displayed for all messages sent by the user including 1369 * direct replies 1370 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1371 * {@link CharSequence} 1372 * 1373 * @deprecated use {@link #EXTRA_MESSAGING_PERSON} 1374 */ 1375 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 1376 1377 /** 1378 * {@link #extras} key: the person to be displayed for all messages sent by the user including 1379 * direct replies 1380 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1381 * {@link Person} 1382 */ 1383 public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; 1384 1385 /** 1386 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 1387 * represented by a {@link android.app.Notification.MessagingStyle} 1388 */ 1389 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 1390 1391 /** @hide */ 1392 public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon"; 1393 1394 /** @hide */ 1395 public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT = 1396 "android.conversationUnreadMessageCount"; 1397 1398 /** 1399 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 1400 * bundles provided by a 1401 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1402 * array of bundles. 1403 */ 1404 public static final String EXTRA_MESSAGES = "android.messages"; 1405 1406 /** 1407 * {@link #extras} key: an array of 1408 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic} 1409 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a 1410 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1411 * array of bundles. 1412 */ 1413 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; 1414 1415 /** 1416 * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification 1417 * represents a group conversation. 1418 */ 1419 public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; 1420 1421 /** 1422 * {@link #extras} key: the type of call represented by the 1423 * {@link android.app.Notification.CallStyle} notification. This extra is an int. 1424 * @hide 1425 */ 1426 public static final String EXTRA_CALL_TYPE = "android.callType"; 1427 1428 /** 1429 * {@link #extras} key: whether the {@link android.app.Notification.CallStyle} notification 1430 * is for a call that will activate video when answered. This extra is a boolean. 1431 */ 1432 public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo"; 1433 1434 /** 1435 * {@link #extras} key: the person to be displayed as calling for the 1436 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}. 1437 */ 1438 public static final String EXTRA_CALL_PERSON = "android.callPerson"; 1439 1440 /** 1441 * {@link #extras} key: the icon to be displayed as a verification status of the caller on a 1442 * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}. 1443 */ 1444 public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon"; 1445 1446 /** 1447 * {@link #extras} key: the text to be displayed as a verification status of the caller on a 1448 * {@link android.app.Notification.CallStyle} notification. This extra is a 1449 * {@link CharSequence}. 1450 */ 1451 public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText"; 1452 1453 /** 1454 * {@link #extras} key: the intent to be sent when the users answers a 1455 * {@link android.app.Notification.CallStyle} notification. This extra is a 1456 * {@link PendingIntent}. 1457 */ 1458 public static final String EXTRA_ANSWER_INTENT = "android.answerIntent"; 1459 1460 /** 1461 * {@link #extras} key: the intent to be sent when the users declines a 1462 * {@link android.app.Notification.CallStyle} notification. This extra is a 1463 * {@link PendingIntent}. 1464 */ 1465 public static final String EXTRA_DECLINE_INTENT = "android.declineIntent"; 1466 1467 /** 1468 * {@link #extras} key: the intent to be sent when the users hangs up a 1469 * {@link android.app.Notification.CallStyle} notification. This extra is a 1470 * {@link PendingIntent}. 1471 */ 1472 public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent"; 1473 1474 /** 1475 * {@link #extras} key: the color used as a hint for the Answer action button of a 1476 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1477 */ 1478 public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; 1479 1480 /** 1481 * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a 1482 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link ColorInt}. 1483 */ 1484 public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; 1485 1486 /** 1487 * {@link #extras} key: whether the notification should be colorized as 1488 * supplied to {@link Builder#setColorized(boolean)}. 1489 */ 1490 public static final String EXTRA_COLORIZED = "android.colorized"; 1491 1492 /** 1493 * @hide 1494 */ 1495 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 1496 1497 /** 1498 * @hide 1499 */ 1500 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 1501 1502 /** 1503 * @hide 1504 */ 1505 public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images"; 1506 1507 /** 1508 * {@link #extras} key: the audio contents of this notification. 1509 * 1510 * This is for use when rendering the notification on an audio-focused interface; 1511 * the audio contents are a complete sound sample that contains the contents/body of the 1512 * notification. This may be used in substitute of a Text-to-Speech reading of the 1513 * notification. For example if the notification represents a voice message this should point 1514 * to the audio of that message. 1515 * 1516 * The data stored under this key should be a String representation of a Uri that contains the 1517 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 1518 * 1519 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 1520 * has a field for holding data URI. That field can be used for audio. 1521 * See {@code Message#setData}. 1522 * 1523 * Example usage: 1524 * <pre> 1525 * {@code 1526 * Notification.Builder myBuilder = (build your Notification as normal); 1527 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 1528 * } 1529 * </pre> 1530 */ 1531 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 1532 1533 /** @hide */ 1534 @SystemApi 1535 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME) 1536 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 1537 1538 /** 1539 * This is set on the notifications shown by system_server about apps running foreground 1540 * services. It indicates that the notification should be shown 1541 * only if any of the given apps do not already have a properly tagged 1542 * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. 1543 * This is a string array of all package names of the apps. 1544 * @hide 1545 */ 1546 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; 1547 1548 @UnsupportedAppUsage 1549 private Icon mSmallIcon; 1550 @UnsupportedAppUsage 1551 private Icon mLargeIcon; 1552 1553 @UnsupportedAppUsage 1554 private String mChannelId; 1555 private long mTimeout; 1556 1557 private String mShortcutId; 1558 private LocusId mLocusId; 1559 private CharSequence mSettingsText; 1560 1561 private BubbleMetadata mBubbleMetadata; 1562 1563 /** @hide */ 1564 @IntDef(prefix = { "GROUP_ALERT_" }, value = { 1565 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY 1566 }) 1567 @Retention(RetentionPolicy.SOURCE) 1568 public @interface GroupAlertBehavior {} 1569 1570 /** 1571 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 1572 * group with sound or vibration ought to make sound or vibrate (respectively), so this 1573 * notification will not be muted when it is in a group. 1574 */ 1575 public static final int GROUP_ALERT_ALL = 0; 1576 1577 /** 1578 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 1579 * notification in a group should be silenced (no sound or vibration) even if they are posted 1580 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to 1581 * mute this notification if this notification is a group child. This must be applied to all 1582 * children notifications you want to mute. 1583 * 1584 * <p> For example, you might want to use this constant if you post a number of children 1585 * notifications at once (say, after a periodic sync), and only need to notify the user 1586 * audibly once. 1587 */ 1588 public static final int GROUP_ALERT_SUMMARY = 1; 1589 1590 /** 1591 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 1592 * notification in a group should be silenced (no sound or vibration) even if they are 1593 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant 1594 * to mute this notification if this notification is a group summary. 1595 * 1596 * <p>For example, you might want to use this constant if only the children notifications 1597 * in your group have content and the summary is only used to visually group notifications 1598 * rather than to alert the user that new information is available. 1599 */ 1600 public static final int GROUP_ALERT_CHILDREN = 2; 1601 1602 private int mGroupAlertBehavior = GROUP_ALERT_ALL; 1603 1604 /** 1605 * If this notification is being shown as a badge, always show as a number. 1606 */ 1607 public static final int BADGE_ICON_NONE = 0; 1608 1609 /** 1610 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to 1611 * represent this notification. 1612 */ 1613 public static final int BADGE_ICON_SMALL = 1; 1614 1615 /** 1616 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to 1617 * represent this notification. 1618 */ 1619 public static final int BADGE_ICON_LARGE = 2; 1620 private int mBadgeIcon = BADGE_ICON_NONE; 1621 1622 /** 1623 * Determines whether the platform can generate contextual actions for a notification. 1624 */ 1625 private boolean mAllowSystemGeneratedContextualActions = true; 1626 1627 /** 1628 * Structure to encapsulate a named action that can be shown as part of this notification. 1629 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1630 * selected by the user. 1631 * <p> 1632 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1633 * or {@link Notification.Builder#addAction(Notification.Action)} 1634 * to attach actions. 1635 * <p> 1636 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link 1637 * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while 1638 * processing broadcast receivers or services in response to notification action clicks. To 1639 * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself. 1640 */ 1641 public static class Action implements Parcelable { 1642 /** 1643 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of 1644 * {@link RemoteInput}s. 1645 * 1646 * This is intended for {@link RemoteInput}s that only accept data, meaning 1647 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 1648 * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not 1649 * empty. These {@link RemoteInput}s will be ignored by devices that do not 1650 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 1651 * 1652 * You can test if a RemoteInput matches these constraints using 1653 * {@link RemoteInput#isDataOnly}. 1654 */ 1655 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; 1656 1657 /** 1658 * {@link }: No semantic action defined. 1659 */ 1660 public static final int SEMANTIC_ACTION_NONE = 0; 1661 1662 /** 1663 * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies 1664 * may be appropriate. 1665 */ 1666 public static final int SEMANTIC_ACTION_REPLY = 1; 1667 1668 /** 1669 * {@code SemanticAction}: Mark content as read. 1670 */ 1671 public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; 1672 1673 /** 1674 * {@code SemanticAction}: Mark content as unread. 1675 */ 1676 public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; 1677 1678 /** 1679 * {@code SemanticAction}: Delete the content associated with the notification. This 1680 * could mean deleting an email, message, etc. 1681 */ 1682 public static final int SEMANTIC_ACTION_DELETE = 4; 1683 1684 /** 1685 * {@code SemanticAction}: Archive the content associated with the notification. This 1686 * could mean archiving an email, message, etc. 1687 */ 1688 public static final int SEMANTIC_ACTION_ARCHIVE = 5; 1689 1690 /** 1691 * {@code SemanticAction}: Mute the content associated with the notification. This could 1692 * mean silencing a conversation or currently playing media. 1693 */ 1694 public static final int SEMANTIC_ACTION_MUTE = 6; 1695 1696 /** 1697 * {@code SemanticAction}: Unmute the content associated with the notification. This could 1698 * mean un-silencing a conversation or currently playing media. 1699 */ 1700 public static final int SEMANTIC_ACTION_UNMUTE = 7; 1701 1702 /** 1703 * {@code SemanticAction}: Mark content with a thumbs up. 1704 */ 1705 public static final int SEMANTIC_ACTION_THUMBS_UP = 8; 1706 1707 /** 1708 * {@code SemanticAction}: Mark content with a thumbs down. 1709 */ 1710 public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; 1711 1712 /** 1713 * {@code SemanticAction}: Call a contact, group, etc. 1714 */ 1715 public static final int SEMANTIC_ACTION_CALL = 10; 1716 1717 /** 1718 * {@code SemanticAction}: Mark the conversation associated with the notification as a 1719 * priority. Note that this is only for use by the notification assistant services. The 1720 * type will be ignored for actions an app adds to its own notifications. 1721 * @hide 1722 */ 1723 @SystemApi 1724 public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; 1725 1726 /** 1727 * {@code SemanticAction}: Mark content as a potential phishing attempt. 1728 * Note that this is only for use by the notification assistant services. The type will 1729 * be ignored for actions an app adds to its own notifications. 1730 * @hide 1731 */ 1732 @SystemApi 1733 public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; 1734 1735 private final Bundle mExtras; 1736 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1737 private Icon mIcon; 1738 private final RemoteInput[] mRemoteInputs; 1739 private boolean mAllowGeneratedReplies = true; 1740 private final @SemanticAction int mSemanticAction; 1741 private final boolean mIsContextual; 1742 private boolean mAuthenticationRequired; 1743 1744 /** 1745 * Small icon representing the action. 1746 * 1747 * @deprecated Use {@link Action#getIcon()} instead. 1748 */ 1749 @Deprecated 1750 public int icon; 1751 1752 /** 1753 * Title of the action. 1754 */ 1755 public CharSequence title; 1756 1757 /** 1758 * Intent to send when the user invokes this action. May be null, in which case the action 1759 * may be rendered in a disabled presentation by the system UI. 1760 */ 1761 public PendingIntent actionIntent; 1762 Action(Parcel in)1763 private Action(Parcel in) { 1764 if (in.readInt() != 0) { 1765 mIcon = Icon.CREATOR.createFromParcel(in); 1766 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1767 icon = mIcon.getResId(); 1768 } 1769 } 1770 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1771 if (in.readInt() == 1) { 1772 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1773 } 1774 mExtras = Bundle.setDefusable(in.readBundle(), true); 1775 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1776 mAllowGeneratedReplies = in.readInt() == 1; 1777 mSemanticAction = in.readInt(); 1778 mIsContextual = in.readInt() == 1; 1779 mAuthenticationRequired = in.readInt() == 1; 1780 } 1781 1782 /** 1783 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1784 */ 1785 @Deprecated Action(int icon, CharSequence title, PendingIntent intent)1786 public Action(int icon, CharSequence title, PendingIntent intent) { 1787 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, 1788 SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */); 1789 } 1790 1791 /** 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)1792 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1793 RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1794 @SemanticAction int semanticAction, boolean isContextual, 1795 boolean requireAuth) { 1796 this.mIcon = icon; 1797 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1798 this.icon = icon.getResId(); 1799 } 1800 this.title = title; 1801 this.actionIntent = intent; 1802 this.mExtras = extras != null ? extras : new Bundle(); 1803 this.mRemoteInputs = remoteInputs; 1804 this.mAllowGeneratedReplies = allowGeneratedReplies; 1805 this.mSemanticAction = semanticAction; 1806 this.mIsContextual = isContextual; 1807 this.mAuthenticationRequired = requireAuth; 1808 } 1809 1810 /** 1811 * Return an icon representing the action. 1812 */ getIcon()1813 public Icon getIcon() { 1814 if (mIcon == null && icon != 0) { 1815 // you snuck an icon in here without using the builder; let's try to keep it 1816 mIcon = Icon.createWithResource("", icon); 1817 } 1818 return mIcon; 1819 } 1820 1821 /** 1822 * Get additional metadata carried around with this Action. 1823 */ getExtras()1824 public Bundle getExtras() { 1825 return mExtras; 1826 } 1827 1828 /** 1829 * Return whether the platform should automatically generate possible replies for this 1830 * {@link Action} 1831 */ getAllowGeneratedReplies()1832 public boolean getAllowGeneratedReplies() { 1833 return mAllowGeneratedReplies; 1834 } 1835 1836 /** 1837 * Get the list of inputs to be collected from the user when this action is sent. 1838 * May return null if no remote inputs were added. Only returns inputs which accept 1839 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 1840 */ getRemoteInputs()1841 public RemoteInput[] getRemoteInputs() { 1842 return mRemoteInputs; 1843 } 1844 1845 /** 1846 * Returns the {@code SemanticAction} associated with this {@link Action}. A 1847 * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do 1848 * (eg. reply, mark as read, delete, etc). 1849 */ getSemanticAction()1850 public @SemanticAction int getSemanticAction() { 1851 return mSemanticAction; 1852 } 1853 1854 /** 1855 * Returns whether this is a contextual Action, i.e. whether the action is dependent on the 1856 * notification message body. An example of a contextual action could be an action opening a 1857 * map application with an address shown in the notification. 1858 */ isContextual()1859 public boolean isContextual() { 1860 return mIsContextual; 1861 } 1862 1863 /** 1864 * Get the list of inputs to be collected from the user that ONLY accept data when this 1865 * action is sent. These remote inputs are guaranteed to return true on a call to 1866 * {@link RemoteInput#isDataOnly}. 1867 * 1868 * Returns null if there are no data-only remote inputs. 1869 * 1870 * This method exists so that legacy RemoteInput collectors that pre-date the addition 1871 * of non-textual RemoteInputs do not access these remote inputs. 1872 */ getDataOnlyRemoteInputs()1873 public RemoteInput[] getDataOnlyRemoteInputs() { 1874 return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 1875 } 1876 1877 /** 1878 * Returns whether the OS should only send this action's {@link PendingIntent} on an 1879 * unlocked device. 1880 * 1881 * If the device is locked when the action is invoked, the OS should show the keyguard and 1882 * require successful authentication before invoking the intent. 1883 */ isAuthenticationRequired()1884 public boolean isAuthenticationRequired() { 1885 return mAuthenticationRequired; 1886 } 1887 1888 /** 1889 * Builder class for {@link Action} objects. 1890 */ 1891 public static final class Builder { 1892 @Nullable private final Icon mIcon; 1893 @Nullable private final CharSequence mTitle; 1894 @Nullable private final PendingIntent mIntent; 1895 private boolean mAllowGeneratedReplies = true; 1896 @NonNull private final Bundle mExtras; 1897 @Nullable private ArrayList<RemoteInput> mRemoteInputs; 1898 private @SemanticAction int mSemanticAction; 1899 private boolean mIsContextual; 1900 private boolean mAuthenticationRequired; 1901 1902 /** 1903 * Construct a new builder for {@link Action} object. 1904 * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, 1905 * action button icons will not be displayed on action buttons, but are still required 1906 * and are available to 1907 * {@link android.service.notification.NotificationListenerService notification listeners}, 1908 * which may display them in other contexts, for example on a wearable device. 1909 * @param icon icon to show for this action 1910 * @param title the title of the action 1911 * @param intent the {@link PendingIntent} to fire when users trigger this action 1912 */ 1913 @Deprecated Builder(int icon, CharSequence title, PendingIntent intent)1914 public Builder(int icon, CharSequence title, PendingIntent intent) { 1915 this(Icon.createWithResource("", icon), title, intent); 1916 } 1917 1918 /** 1919 * Construct a new builder for {@link Action} object. 1920 * 1921 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 1922 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 1923 * while processing broadcast receivers or services in response to notification action 1924 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the 1925 * activity itself. 1926 * 1927 * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or 1928 * both are displayed or required, depends on where and how the action is used, and the 1929 * {@link Style} applied to the Notification. 1930 * 1931 * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons 1932 * will not be displayed on action buttons, but are still required and are available 1933 * to {@link android.service.notification.NotificationListenerService notification 1934 * listeners}, which may display them in other contexts, for example on a wearable 1935 * device. 1936 * 1937 * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a 1938 * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed 1939 * with an altered in luminance to ensure proper contrast within the Notification. 1940 * 1941 * @param icon icon to show for this action 1942 * @param title the title of the action 1943 * @param intent the {@link PendingIntent} to fire when users trigger this action 1944 */ Builder(Icon icon, CharSequence title, PendingIntent intent)1945 public Builder(Icon icon, CharSequence title, PendingIntent intent) { 1946 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false); 1947 } 1948 1949 /** 1950 * Construct a new builder for {@link Action} object using the fields from an 1951 * {@link Action}. 1952 * @param action the action to read fields from. 1953 */ Builder(Action action)1954 public Builder(Action action) { 1955 this(action.getIcon(), action.title, action.actionIntent, 1956 new Bundle(action.mExtras), action.getRemoteInputs(), 1957 action.getAllowGeneratedReplies(), action.getSemanticAction(), 1958 action.isAuthenticationRequired()); 1959 } 1960 Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)1961 private Builder(@Nullable Icon icon, @Nullable CharSequence title, 1962 @Nullable PendingIntent intent, @NonNull Bundle extras, 1963 @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1964 @SemanticAction int semanticAction, boolean authRequired) { 1965 mIcon = icon; 1966 mTitle = title; 1967 mIntent = intent; 1968 mExtras = extras; 1969 if (remoteInputs != null) { 1970 mRemoteInputs = new ArrayList<>(remoteInputs.length); 1971 Collections.addAll(mRemoteInputs, remoteInputs); 1972 } 1973 mAllowGeneratedReplies = allowGeneratedReplies; 1974 mSemanticAction = semanticAction; 1975 mAuthenticationRequired = authRequired; 1976 } 1977 1978 /** 1979 * Merge additional metadata into this builder. 1980 * 1981 * <p>Values within the Bundle will replace existing extras values in this Builder. 1982 * 1983 * @see Notification.Action#extras 1984 */ 1985 @NonNull addExtras(Bundle extras)1986 public Builder addExtras(Bundle extras) { 1987 if (extras != null) { 1988 mExtras.putAll(extras); 1989 } 1990 return this; 1991 } 1992 1993 /** 1994 * Get the metadata Bundle used by this Builder. 1995 * 1996 * <p>The returned Bundle is shared with this Builder. 1997 */ 1998 @NonNull getExtras()1999 public Bundle getExtras() { 2000 return mExtras; 2001 } 2002 2003 /** 2004 * Add an input to be collected from the user when this action is sent. 2005 * Response values can be retrieved from the fired intent by using the 2006 * {@link RemoteInput#getResultsFromIntent} function. 2007 * @param remoteInput a {@link RemoteInput} to add to the action 2008 * @return this object for method chaining 2009 */ 2010 @NonNull addRemoteInput(RemoteInput remoteInput)2011 public Builder addRemoteInput(RemoteInput remoteInput) { 2012 if (mRemoteInputs == null) { 2013 mRemoteInputs = new ArrayList<RemoteInput>(); 2014 } 2015 mRemoteInputs.add(remoteInput); 2016 return this; 2017 } 2018 2019 /** 2020 * Set whether the platform should automatically generate possible replies to add to 2021 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 2022 * {@link RemoteInput}, this has no effect. 2023 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 2024 * otherwise 2025 * @return this object for method chaining 2026 * The default value is {@code true} 2027 */ 2028 @NonNull setAllowGeneratedReplies(boolean allowGeneratedReplies)2029 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 2030 mAllowGeneratedReplies = allowGeneratedReplies; 2031 return this; 2032 } 2033 2034 /** 2035 * Sets the {@code SemanticAction} for this {@link Action}. A 2036 * {@code SemanticAction} denotes what an {@link Action}'s 2037 * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). 2038 * @param semanticAction a SemanticAction defined within {@link Action} with 2039 * {@code SEMANTIC_ACTION_} prefixes 2040 * @return this object for method chaining 2041 */ 2042 @NonNull setSemanticAction(@emanticAction int semanticAction)2043 public Builder setSemanticAction(@SemanticAction int semanticAction) { 2044 mSemanticAction = semanticAction; 2045 return this; 2046 } 2047 2048 /** 2049 * Sets whether this {@link Action} is a contextual action, i.e. whether the action is 2050 * dependent on the notification message body. An example of a contextual action could 2051 * be an action opening a map application with an address shown in the notification. 2052 */ 2053 @NonNull setContextual(boolean isContextual)2054 public Builder setContextual(boolean isContextual) { 2055 mIsContextual = isContextual; 2056 return this; 2057 } 2058 2059 /** 2060 * Apply an extender to this action builder. Extenders may be used to add 2061 * metadata or change options on this builder. 2062 */ 2063 @NonNull extend(Extender extender)2064 public Builder extend(Extender extender) { 2065 extender.extend(this); 2066 return this; 2067 } 2068 2069 /** 2070 * Sets whether the OS should only send this action's {@link PendingIntent} on an 2071 * unlocked device. 2072 * 2073 * If this is true and the device is locked when the action is invoked, the OS will 2074 * show the keyguard and require successful authentication before invoking the intent. 2075 * If this is false and the device is locked, the OS will decide whether authentication 2076 * should be required. 2077 */ 2078 @NonNull setAuthenticationRequired(boolean authenticationRequired)2079 public Builder setAuthenticationRequired(boolean authenticationRequired) { 2080 mAuthenticationRequired = authenticationRequired; 2081 return this; 2082 } 2083 2084 /** 2085 * Throws an NPE if we are building a contextual action missing one of the fields 2086 * necessary to display the action. 2087 */ checkContextualActionNullFields()2088 private void checkContextualActionNullFields() { 2089 if (!mIsContextual) return; 2090 2091 if (mIcon == null) { 2092 throw new NullPointerException("Contextual Actions must contain a valid icon"); 2093 } 2094 2095 if (mIntent == null) { 2096 throw new NullPointerException( 2097 "Contextual Actions must contain a valid PendingIntent"); 2098 } 2099 } 2100 2101 /** 2102 * Combine all of the options that have been set and return a new {@link Action} 2103 * object. 2104 * @return the built action 2105 */ 2106 @NonNull build()2107 public Action build() { 2108 checkContextualActionNullFields(); 2109 2110 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>(); 2111 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle( 2112 mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 2113 if (previousDataInputs != null) { 2114 for (RemoteInput input : previousDataInputs) { 2115 dataOnlyInputs.add(input); 2116 } 2117 } 2118 List<RemoteInput> textInputs = new ArrayList<>(); 2119 if (mRemoteInputs != null) { 2120 for (RemoteInput input : mRemoteInputs) { 2121 if (input.isDataOnly()) { 2122 dataOnlyInputs.add(input); 2123 } else { 2124 textInputs.add(input); 2125 } 2126 } 2127 } 2128 if (!dataOnlyInputs.isEmpty()) { 2129 RemoteInput[] dataInputsArr = 2130 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 2131 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); 2132 } 2133 RemoteInput[] textInputsArr = textInputs.isEmpty() 2134 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 2135 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 2136 mAllowGeneratedReplies, mSemanticAction, mIsContextual, 2137 mAuthenticationRequired); 2138 } 2139 } 2140 2141 @Override clone()2142 public Action clone() { 2143 return new Action( 2144 getIcon(), 2145 title, 2146 actionIntent, // safe to alias 2147 mExtras == null ? new Bundle() : new Bundle(mExtras), 2148 getRemoteInputs(), 2149 getAllowGeneratedReplies(), 2150 getSemanticAction(), 2151 isContextual(), 2152 isAuthenticationRequired()); 2153 } 2154 2155 @Override describeContents()2156 public int describeContents() { 2157 return 0; 2158 } 2159 2160 @Override writeToParcel(Parcel out, int flags)2161 public void writeToParcel(Parcel out, int flags) { 2162 final Icon ic = getIcon(); 2163 if (ic != null) { 2164 out.writeInt(1); 2165 ic.writeToParcel(out, 0); 2166 } else { 2167 out.writeInt(0); 2168 } 2169 TextUtils.writeToParcel(title, out, flags); 2170 if (actionIntent != null) { 2171 out.writeInt(1); 2172 actionIntent.writeToParcel(out, flags); 2173 } else { 2174 out.writeInt(0); 2175 } 2176 out.writeBundle(mExtras); 2177 out.writeTypedArray(mRemoteInputs, flags); 2178 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 2179 out.writeInt(mSemanticAction); 2180 out.writeInt(mIsContextual ? 1 : 0); 2181 out.writeInt(mAuthenticationRequired ? 1 : 0); 2182 } 2183 2184 public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR = 2185 new Parcelable.Creator<Action>() { 2186 public Action createFromParcel(Parcel in) { 2187 return new Action(in); 2188 } 2189 public Action[] newArray(int size) { 2190 return new Action[size]; 2191 } 2192 }; 2193 2194 /** 2195 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 2196 * metadata or change options on an action builder. 2197 */ 2198 public interface Extender { 2199 /** 2200 * Apply this extender to a notification action builder. 2201 * @param builder the builder to be modified. 2202 * @return the build object for chaining. 2203 */ extend(Builder builder)2204 public Builder extend(Builder builder); 2205 } 2206 2207 /** 2208 * Wearable extender for notification actions. To add extensions to an action, 2209 * create a new {@link android.app.Notification.Action.WearableExtender} object using 2210 * the {@code WearableExtender()} constructor and apply it to a 2211 * {@link android.app.Notification.Action.Builder} using 2212 * {@link android.app.Notification.Action.Builder#extend}. 2213 * 2214 * <pre class="prettyprint"> 2215 * Notification.Action action = new Notification.Action.Builder( 2216 * R.drawable.archive_all, "Archive all", actionIntent) 2217 * .extend(new Notification.Action.WearableExtender() 2218 * .setAvailableOffline(false)) 2219 * .build();</pre> 2220 */ 2221 public static final class WearableExtender implements Extender { 2222 /** Notification action extra which contains wearable extensions */ 2223 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 2224 2225 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 2226 private static final String KEY_FLAGS = "flags"; 2227 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 2228 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 2229 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 2230 2231 // Flags bitwise-ored to mFlags 2232 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 2233 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 2234 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 2235 2236 // Default value for flags integer 2237 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 2238 2239 private int mFlags = DEFAULT_FLAGS; 2240 2241 private CharSequence mInProgressLabel; 2242 private CharSequence mConfirmLabel; 2243 private CharSequence mCancelLabel; 2244 2245 /** 2246 * Create a {@link android.app.Notification.Action.WearableExtender} with default 2247 * options. 2248 */ WearableExtender()2249 public WearableExtender() { 2250 } 2251 2252 /** 2253 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 2254 * wearable options present in an existing notification action. 2255 * @param action the notification action to inspect. 2256 */ WearableExtender(Action action)2257 public WearableExtender(Action action) { 2258 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 2259 if (wearableBundle != null) { 2260 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 2261 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 2262 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 2263 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 2264 } 2265 } 2266 2267 /** 2268 * Apply wearable extensions to a notification action that is being built. This is 2269 * typically called by the {@link android.app.Notification.Action.Builder#extend} 2270 * method of {@link android.app.Notification.Action.Builder}. 2271 */ 2272 @Override extend(Action.Builder builder)2273 public Action.Builder extend(Action.Builder builder) { 2274 Bundle wearableBundle = new Bundle(); 2275 2276 if (mFlags != DEFAULT_FLAGS) { 2277 wearableBundle.putInt(KEY_FLAGS, mFlags); 2278 } 2279 if (mInProgressLabel != null) { 2280 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 2281 } 2282 if (mConfirmLabel != null) { 2283 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 2284 } 2285 if (mCancelLabel != null) { 2286 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 2287 } 2288 2289 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 2290 return builder; 2291 } 2292 2293 @Override clone()2294 public WearableExtender clone() { 2295 WearableExtender that = new WearableExtender(); 2296 that.mFlags = this.mFlags; 2297 that.mInProgressLabel = this.mInProgressLabel; 2298 that.mConfirmLabel = this.mConfirmLabel; 2299 that.mCancelLabel = this.mCancelLabel; 2300 return that; 2301 } 2302 2303 /** 2304 * Set whether this action is available when the wearable device is not connected to 2305 * a companion device. The user can still trigger this action when the wearable device is 2306 * offline, but a visual hint will indicate that the action may not be available. 2307 * Defaults to true. 2308 */ setAvailableOffline(boolean availableOffline)2309 public WearableExtender setAvailableOffline(boolean availableOffline) { 2310 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 2311 return this; 2312 } 2313 2314 /** 2315 * Get whether this action is available when the wearable device is not connected to 2316 * a companion device. The user can still trigger this action when the wearable device is 2317 * offline, but a visual hint will indicate that the action may not be available. 2318 * Defaults to true. 2319 */ isAvailableOffline()2320 public boolean isAvailableOffline() { 2321 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 2322 } 2323 setFlag(int mask, boolean value)2324 private void setFlag(int mask, boolean value) { 2325 if (value) { 2326 mFlags |= mask; 2327 } else { 2328 mFlags &= ~mask; 2329 } 2330 } 2331 2332 /** 2333 * Set a label to display while the wearable is preparing to automatically execute the 2334 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2335 * 2336 * @param label the label to display while the action is being prepared to execute 2337 * @return this object for method chaining 2338 */ 2339 @Deprecated setInProgressLabel(CharSequence label)2340 public WearableExtender setInProgressLabel(CharSequence label) { 2341 mInProgressLabel = label; 2342 return this; 2343 } 2344 2345 /** 2346 * Get the label to display while the wearable is preparing to automatically execute 2347 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2348 * 2349 * @return the label to display while the action is being prepared to execute 2350 */ 2351 @Deprecated getInProgressLabel()2352 public CharSequence getInProgressLabel() { 2353 return mInProgressLabel; 2354 } 2355 2356 /** 2357 * Set a label to display to confirm that the action should be executed. 2358 * This is usually an imperative verb like "Send". 2359 * 2360 * @param label the label to confirm the action should be executed 2361 * @return this object for method chaining 2362 */ 2363 @Deprecated setConfirmLabel(CharSequence label)2364 public WearableExtender setConfirmLabel(CharSequence label) { 2365 mConfirmLabel = label; 2366 return this; 2367 } 2368 2369 /** 2370 * Get the label to display to confirm that the action should be executed. 2371 * This is usually an imperative verb like "Send". 2372 * 2373 * @return the label to confirm the action should be executed 2374 */ 2375 @Deprecated getConfirmLabel()2376 public CharSequence getConfirmLabel() { 2377 return mConfirmLabel; 2378 } 2379 2380 /** 2381 * Set a label to display to cancel the action. 2382 * This is usually an imperative verb, like "Cancel". 2383 * 2384 * @param label the label to display to cancel the action 2385 * @return this object for method chaining 2386 */ 2387 @Deprecated setCancelLabel(CharSequence label)2388 public WearableExtender setCancelLabel(CharSequence label) { 2389 mCancelLabel = label; 2390 return this; 2391 } 2392 2393 /** 2394 * Get the label to display to cancel the action. 2395 * This is usually an imperative verb like "Cancel". 2396 * 2397 * @return the label to display to cancel the action 2398 */ 2399 @Deprecated getCancelLabel()2400 public CharSequence getCancelLabel() { 2401 return mCancelLabel; 2402 } 2403 2404 /** 2405 * Set a hint that this Action will launch an {@link Activity} directly, telling the 2406 * platform that it can generate the appropriate transitions. 2407 * @param hintLaunchesActivity {@code true} if the content intent will launch 2408 * an activity and transitions should be generated, false otherwise. 2409 * @return this object for method chaining 2410 */ setHintLaunchesActivity( boolean hintLaunchesActivity)2411 public WearableExtender setHintLaunchesActivity( 2412 boolean hintLaunchesActivity) { 2413 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 2414 return this; 2415 } 2416 2417 /** 2418 * Get a hint that this Action will launch an {@link Activity} directly, telling the 2419 * platform that it can generate the appropriate transitions 2420 * @return {@code true} if the content intent will launch an activity and transitions 2421 * should be generated, false otherwise. The default value is {@code false} if this was 2422 * never set. 2423 */ getHintLaunchesActivity()2424 public boolean getHintLaunchesActivity() { 2425 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 2426 } 2427 2428 /** 2429 * Set a hint that this Action should be displayed inline. 2430 * 2431 * @param hintDisplayInline {@code true} if action should be displayed inline, false 2432 * otherwise 2433 * @return this object for method chaining 2434 */ setHintDisplayActionInline( boolean hintDisplayInline)2435 public WearableExtender setHintDisplayActionInline( 2436 boolean hintDisplayInline) { 2437 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 2438 return this; 2439 } 2440 2441 /** 2442 * Get a hint that this Action should be displayed inline. 2443 * 2444 * @return {@code true} if the Action should be displayed inline, {@code false} 2445 * otherwise. The default value is {@code false} if this was never set. 2446 */ getHintDisplayActionInline()2447 public boolean getHintDisplayActionInline() { 2448 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 2449 } 2450 } 2451 2452 /** 2453 * Provides meaning to an {@link Action} that hints at what the associated 2454 * {@link PendingIntent} will do. For example, an {@link Action} with a 2455 * {@link PendingIntent} that replies to a text message notification may have the 2456 * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. 2457 * 2458 * @hide 2459 */ 2460 @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { 2461 SEMANTIC_ACTION_NONE, 2462 SEMANTIC_ACTION_REPLY, 2463 SEMANTIC_ACTION_MARK_AS_READ, 2464 SEMANTIC_ACTION_MARK_AS_UNREAD, 2465 SEMANTIC_ACTION_DELETE, 2466 SEMANTIC_ACTION_ARCHIVE, 2467 SEMANTIC_ACTION_MUTE, 2468 SEMANTIC_ACTION_UNMUTE, 2469 SEMANTIC_ACTION_THUMBS_UP, 2470 SEMANTIC_ACTION_THUMBS_DOWN, 2471 SEMANTIC_ACTION_CALL, 2472 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY, 2473 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING 2474 }) 2475 @Retention(RetentionPolicy.SOURCE) 2476 public @interface SemanticAction {} 2477 } 2478 2479 /** 2480 * Array of all {@link Action} structures attached to this notification by 2481 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 2482 * {@link android.service.notification.NotificationListenerService} that provide an alternative 2483 * interface for invoking actions. 2484 */ 2485 public Action[] actions; 2486 2487 /** 2488 * Replacement version of this notification whose content will be shown 2489 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 2490 * and {@link #VISIBILITY_PUBLIC}. 2491 */ 2492 public Notification publicVersion; 2493 2494 /** 2495 * Constructs a Notification object with default values. 2496 * You might want to consider using {@link Builder} instead. 2497 */ Notification()2498 public Notification() 2499 { 2500 this.when = System.currentTimeMillis(); 2501 this.creationTime = System.currentTimeMillis(); 2502 this.priority = PRIORITY_DEFAULT; 2503 } 2504 2505 /** 2506 * @hide 2507 */ 2508 @UnsupportedAppUsage Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2509 public Notification(Context context, int icon, CharSequence tickerText, long when, 2510 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 2511 { 2512 new Builder(context) 2513 .setWhen(when) 2514 .setSmallIcon(icon) 2515 .setTicker(tickerText) 2516 .setContentTitle(contentTitle) 2517 .setContentText(contentText) 2518 .setContentIntent(PendingIntent.getActivity( 2519 context, 0, contentIntent, PendingIntent.FLAG_MUTABLE)) 2520 .buildInto(this); 2521 } 2522 2523 /** 2524 * Constructs a Notification object with the information needed to 2525 * have a status bar icon without the standard expanded view. 2526 * 2527 * @param icon The resource id of the icon to put in the status bar. 2528 * @param tickerText The text that flows by in the status bar when the notification first 2529 * activates. 2530 * @param when The time to show in the time field. In the System.currentTimeMillis 2531 * timebase. 2532 * 2533 * @deprecated Use {@link Builder} instead. 2534 */ 2535 @Deprecated Notification(int icon, CharSequence tickerText, long when)2536 public Notification(int icon, CharSequence tickerText, long when) 2537 { 2538 this.icon = icon; 2539 this.tickerText = tickerText; 2540 this.when = when; 2541 this.creationTime = System.currentTimeMillis(); 2542 } 2543 2544 /** 2545 * Unflatten the notification from a parcel. 2546 */ 2547 @SuppressWarnings("unchecked") Notification(Parcel parcel)2548 public Notification(Parcel parcel) { 2549 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 2550 // intents in extras are always written as the last entry. 2551 readFromParcelImpl(parcel); 2552 // Must be read last! 2553 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 2554 } 2555 readFromParcelImpl(Parcel parcel)2556 private void readFromParcelImpl(Parcel parcel) 2557 { 2558 int version = parcel.readInt(); 2559 2560 mAllowlistToken = parcel.readStrongBinder(); 2561 if (mAllowlistToken == null) { 2562 mAllowlistToken = processAllowlistToken; 2563 } 2564 // Propagate this token to all pending intents that are unmarshalled from the parcel. 2565 parcel.setClassCookie(PendingIntent.class, mAllowlistToken); 2566 2567 when = parcel.readLong(); 2568 creationTime = parcel.readLong(); 2569 if (parcel.readInt() != 0) { 2570 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 2571 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 2572 icon = mSmallIcon.getResId(); 2573 } 2574 } 2575 number = parcel.readInt(); 2576 if (parcel.readInt() != 0) { 2577 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2578 } 2579 if (parcel.readInt() != 0) { 2580 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2581 } 2582 if (parcel.readInt() != 0) { 2583 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2584 } 2585 if (parcel.readInt() != 0) { 2586 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 2587 } 2588 if (parcel.readInt() != 0) { 2589 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 2590 } 2591 if (parcel.readInt() != 0) { 2592 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 2593 } 2594 defaults = parcel.readInt(); 2595 flags = parcel.readInt(); 2596 if (parcel.readInt() != 0) { 2597 sound = Uri.CREATOR.createFromParcel(parcel); 2598 } 2599 2600 audioStreamType = parcel.readInt(); 2601 if (parcel.readInt() != 0) { 2602 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 2603 } 2604 vibrate = parcel.createLongArray(); 2605 ledARGB = parcel.readInt(); 2606 ledOnMS = parcel.readInt(); 2607 ledOffMS = parcel.readInt(); 2608 iconLevel = parcel.readInt(); 2609 2610 if (parcel.readInt() != 0) { 2611 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2612 } 2613 2614 priority = parcel.readInt(); 2615 2616 category = parcel.readString8(); 2617 2618 mGroupKey = parcel.readString8(); 2619 2620 mSortKey = parcel.readString8(); 2621 2622 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 2623 fixDuplicateExtras(); 2624 2625 actions = parcel.createTypedArray(Action.CREATOR); // may be null 2626 2627 if (parcel.readInt() != 0) { 2628 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2629 } 2630 2631 if (parcel.readInt() != 0) { 2632 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2633 } 2634 2635 visibility = parcel.readInt(); 2636 2637 if (parcel.readInt() != 0) { 2638 publicVersion = Notification.CREATOR.createFromParcel(parcel); 2639 } 2640 2641 color = parcel.readInt(); 2642 2643 if (parcel.readInt() != 0) { 2644 mChannelId = parcel.readString8(); 2645 } 2646 mTimeout = parcel.readLong(); 2647 2648 if (parcel.readInt() != 0) { 2649 mShortcutId = parcel.readString8(); 2650 } 2651 2652 if (parcel.readInt() != 0) { 2653 mLocusId = LocusId.CREATOR.createFromParcel(parcel); 2654 } 2655 2656 mBadgeIcon = parcel.readInt(); 2657 2658 if (parcel.readInt() != 0) { 2659 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2660 } 2661 2662 mGroupAlertBehavior = parcel.readInt(); 2663 if (parcel.readInt() != 0) { 2664 mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel); 2665 } 2666 2667 mAllowSystemGeneratedContextualActions = parcel.readBoolean(); 2668 2669 mFgsDeferBehavior = parcel.readInt(); 2670 } 2671 2672 @Override clone()2673 public Notification clone() { 2674 Notification that = new Notification(); 2675 cloneInto(that, true); 2676 return that; 2677 } 2678 2679 /** 2680 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 2681 * of this into that. 2682 * @hide 2683 */ cloneInto(Notification that, boolean heavy)2684 public void cloneInto(Notification that, boolean heavy) { 2685 that.mAllowlistToken = this.mAllowlistToken; 2686 that.when = this.when; 2687 that.creationTime = this.creationTime; 2688 that.mSmallIcon = this.mSmallIcon; 2689 that.number = this.number; 2690 2691 // PendingIntents are global, so there's no reason (or way) to clone them. 2692 that.contentIntent = this.contentIntent; 2693 that.deleteIntent = this.deleteIntent; 2694 that.fullScreenIntent = this.fullScreenIntent; 2695 2696 if (this.tickerText != null) { 2697 that.tickerText = this.tickerText.toString(); 2698 } 2699 if (heavy && this.tickerView != null) { 2700 that.tickerView = this.tickerView.clone(); 2701 } 2702 if (heavy && this.contentView != null) { 2703 that.contentView = this.contentView.clone(); 2704 } 2705 if (heavy && this.mLargeIcon != null) { 2706 that.mLargeIcon = this.mLargeIcon; 2707 } 2708 that.iconLevel = this.iconLevel; 2709 that.sound = this.sound; // android.net.Uri is immutable 2710 that.audioStreamType = this.audioStreamType; 2711 if (this.audioAttributes != null) { 2712 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 2713 } 2714 2715 final long[] vibrate = this.vibrate; 2716 if (vibrate != null) { 2717 final int N = vibrate.length; 2718 final long[] vib = that.vibrate = new long[N]; 2719 System.arraycopy(vibrate, 0, vib, 0, N); 2720 } 2721 2722 that.ledARGB = this.ledARGB; 2723 that.ledOnMS = this.ledOnMS; 2724 that.ledOffMS = this.ledOffMS; 2725 that.defaults = this.defaults; 2726 2727 that.flags = this.flags; 2728 2729 that.priority = this.priority; 2730 2731 that.category = this.category; 2732 2733 that.mGroupKey = this.mGroupKey; 2734 2735 that.mSortKey = this.mSortKey; 2736 2737 if (this.extras != null) { 2738 try { 2739 that.extras = new Bundle(this.extras); 2740 // will unparcel 2741 that.extras.size(); 2742 } catch (BadParcelableException e) { 2743 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 2744 that.extras = null; 2745 } 2746 } 2747 2748 if (!ArrayUtils.isEmpty(allPendingIntents)) { 2749 that.allPendingIntents = new ArraySet<>(allPendingIntents); 2750 } 2751 2752 if (this.actions != null) { 2753 that.actions = new Action[this.actions.length]; 2754 for(int i=0; i<this.actions.length; i++) { 2755 if ( this.actions[i] != null) { 2756 that.actions[i] = this.actions[i].clone(); 2757 } 2758 } 2759 } 2760 2761 if (heavy && this.bigContentView != null) { 2762 that.bigContentView = this.bigContentView.clone(); 2763 } 2764 2765 if (heavy && this.headsUpContentView != null) { 2766 that.headsUpContentView = this.headsUpContentView.clone(); 2767 } 2768 2769 that.visibility = this.visibility; 2770 2771 if (this.publicVersion != null) { 2772 that.publicVersion = new Notification(); 2773 this.publicVersion.cloneInto(that.publicVersion, heavy); 2774 } 2775 2776 that.color = this.color; 2777 2778 that.mChannelId = this.mChannelId; 2779 that.mTimeout = this.mTimeout; 2780 that.mShortcutId = this.mShortcutId; 2781 that.mLocusId = this.mLocusId; 2782 that.mBadgeIcon = this.mBadgeIcon; 2783 that.mSettingsText = this.mSettingsText; 2784 that.mGroupAlertBehavior = this.mGroupAlertBehavior; 2785 that.mFgsDeferBehavior = this.mFgsDeferBehavior; 2786 that.mBubbleMetadata = this.mBubbleMetadata; 2787 that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; 2788 2789 if (!heavy) { 2790 that.lightenPayload(); // will clean out extras 2791 } 2792 } 2793 visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2794 private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) { 2795 if (icon == null) return; 2796 final int iconType = icon.getType(); 2797 if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) { 2798 visitor.accept(icon.getUri()); 2799 } 2800 } 2801 2802 /** 2803 * Note all {@link Uri} that are referenced internally, with the expectation 2804 * that Uri permission grants will need to be issued to ensure the recipient 2805 * of this object is able to render its contents. 2806 * 2807 * @hide 2808 */ visitUris(@onNull Consumer<Uri> visitor)2809 public void visitUris(@NonNull Consumer<Uri> visitor) { 2810 if (publicVersion != null) { 2811 publicVersion.visitUris(visitor); 2812 } 2813 2814 visitor.accept(sound); 2815 2816 if (tickerView != null) tickerView.visitUris(visitor); 2817 if (contentView != null) contentView.visitUris(visitor); 2818 if (bigContentView != null) bigContentView.visitUris(visitor); 2819 if (headsUpContentView != null) headsUpContentView.visitUris(visitor); 2820 2821 visitIconUri(visitor, mSmallIcon); 2822 visitIconUri(visitor, mLargeIcon); 2823 2824 if (actions != null) { 2825 for (Action action : actions) { 2826 visitIconUri(visitor, action.getIcon()); 2827 } 2828 } 2829 2830 if (extras != null) { 2831 visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class)); 2832 visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class)); 2833 2834 // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a 2835 // String representation of a Uri, but the previous implementation (and unit test) of 2836 // this method has always treated it as a Uri object. Given the inconsistency, 2837 // supporting both going forward is the safest choice. 2838 Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI); 2839 if (audioContentsUri instanceof Uri) { 2840 visitor.accept((Uri) audioContentsUri); 2841 } else if (audioContentsUri instanceof String) { 2842 visitor.accept(Uri.parse((String) audioContentsUri)); 2843 } 2844 2845 if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 2846 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); 2847 } 2848 2849 ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 2850 if (people != null && !people.isEmpty()) { 2851 for (Person p : people) { 2852 visitor.accept(p.getIconUri()); 2853 } 2854 } 2855 2856 final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); 2857 if (person != null) { 2858 visitor.accept(person.getIconUri()); 2859 } 2860 2861 final RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[]) 2862 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS); 2863 if (history != null) { 2864 for (int i = 0; i < history.length; i++) { 2865 RemoteInputHistoryItem item = history[i]; 2866 if (item.getUri() != null) { 2867 visitor.accept(item.getUri()); 2868 } 2869 } 2870 } 2871 } 2872 2873 if (isStyle(MessagingStyle.class) && extras != null) { 2874 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 2875 if (!ArrayUtils.isEmpty(messages)) { 2876 for (MessagingStyle.Message message : MessagingStyle.Message 2877 .getMessagesFromBundleArray(messages)) { 2878 visitor.accept(message.getDataUri()); 2879 2880 Person senderPerson = message.getSenderPerson(); 2881 if (senderPerson != null) { 2882 visitor.accept(senderPerson.getIconUri()); 2883 } 2884 } 2885 } 2886 2887 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 2888 if (!ArrayUtils.isEmpty(historic)) { 2889 for (MessagingStyle.Message message : MessagingStyle.Message 2890 .getMessagesFromBundleArray(historic)) { 2891 visitor.accept(message.getDataUri()); 2892 2893 Person senderPerson = message.getSenderPerson(); 2894 if (senderPerson != null) { 2895 visitor.accept(senderPerson.getIconUri()); 2896 } 2897 } 2898 } 2899 2900 visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON)); 2901 } 2902 2903 if (isStyle(CallStyle.class) & extras != null) { 2904 Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON); 2905 if (callPerson != null) { 2906 visitor.accept(callPerson.getIconUri()); 2907 } 2908 visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON)); 2909 } 2910 2911 if (mBubbleMetadata != null) { 2912 visitIconUri(visitor, mBubbleMetadata.getIcon()); 2913 } 2914 } 2915 2916 /** 2917 * Removes heavyweight parts of the Notification object for archival or for sending to 2918 * listeners when the full contents are not necessary. 2919 * @hide 2920 */ lightenPayload()2921 public final void lightenPayload() { 2922 tickerView = null; 2923 contentView = null; 2924 bigContentView = null; 2925 headsUpContentView = null; 2926 mLargeIcon = null; 2927 if (extras != null && !extras.isEmpty()) { 2928 final Set<String> keyset = extras.keySet(); 2929 final int N = keyset.size(); 2930 final String[] keys = keyset.toArray(new String[N]); 2931 for (int i=0; i<N; i++) { 2932 final String key = keys[i]; 2933 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) { 2934 continue; 2935 } 2936 final Object obj = extras.get(key); 2937 if (obj != null && 2938 ( obj instanceof Parcelable 2939 || obj instanceof Parcelable[] 2940 || obj instanceof SparseArray 2941 || obj instanceof ArrayList)) { 2942 extras.remove(key); 2943 } 2944 } 2945 } 2946 } 2947 2948 /** 2949 * Make sure this CharSequence is safe to put into a bundle, which basically 2950 * means it had better not be some custom Parcelable implementation. 2951 * @hide 2952 */ safeCharSequence(CharSequence cs)2953 public static CharSequence safeCharSequence(CharSequence cs) { 2954 if (cs == null) return cs; 2955 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 2956 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 2957 } 2958 if (cs instanceof Parcelable) { 2959 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 2960 + " instance is a custom Parcelable and not allowed in Notification"); 2961 return cs.toString(); 2962 } 2963 return removeTextSizeSpans(cs); 2964 } 2965 removeTextSizeSpans(CharSequence charSequence)2966 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 2967 if (charSequence instanceof Spanned) { 2968 Spanned ss = (Spanned) charSequence; 2969 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 2970 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 2971 for (Object span : spans) { 2972 Object resultSpan = span; 2973 if (resultSpan instanceof CharacterStyle) { 2974 resultSpan = ((CharacterStyle) span).getUnderlying(); 2975 } 2976 if (resultSpan instanceof TextAppearanceSpan) { 2977 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 2978 resultSpan = new TextAppearanceSpan( 2979 originalSpan.getFamily(), 2980 originalSpan.getTextStyle(), 2981 -1, 2982 originalSpan.getTextColor(), 2983 originalSpan.getLinkTextColor()); 2984 } else if (resultSpan instanceof RelativeSizeSpan 2985 || resultSpan instanceof AbsoluteSizeSpan) { 2986 continue; 2987 } else { 2988 resultSpan = span; 2989 } 2990 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 2991 ss.getSpanFlags(span)); 2992 } 2993 return builder; 2994 } 2995 return charSequence; 2996 } 2997 describeContents()2998 public int describeContents() { 2999 return 0; 3000 } 3001 3002 /** 3003 * Flatten this notification into a parcel. 3004 */ writeToParcel(Parcel parcel, int flags)3005 public void writeToParcel(Parcel parcel, int flags) { 3006 // We need to mark all pending intents getting into the notification 3007 // system as being put there to later allow the notification ranker 3008 // to launch them and by doing so add the app to the battery saver white 3009 // list for a short period of time. The problem is that the system 3010 // cannot look into the extras as there may be parcelables there that 3011 // the platform does not know how to handle. To go around that we have 3012 // an explicit list of the pending intents in the extras bundle. 3013 final boolean collectPendingIntents = (allPendingIntents == null); 3014 if (collectPendingIntents) { 3015 PendingIntent.setOnMarshaledListener( 3016 (PendingIntent intent, Parcel out, int outFlags) -> { 3017 if (parcel == out) { 3018 synchronized (this) { 3019 if (allPendingIntents == null) { 3020 allPendingIntents = new ArraySet<>(); 3021 } 3022 allPendingIntents.add(intent); 3023 } 3024 } 3025 }); 3026 } 3027 try { 3028 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 3029 // want to intercept all pending events written to the parcel. 3030 writeToParcelImpl(parcel, flags); 3031 synchronized (this) { 3032 // Must be written last! 3033 parcel.writeArraySet(allPendingIntents); 3034 } 3035 } finally { 3036 if (collectPendingIntents) { 3037 PendingIntent.setOnMarshaledListener(null); 3038 } 3039 } 3040 } 3041 writeToParcelImpl(Parcel parcel, int flags)3042 private void writeToParcelImpl(Parcel parcel, int flags) { 3043 parcel.writeInt(1); 3044 3045 parcel.writeStrongBinder(mAllowlistToken); 3046 parcel.writeLong(when); 3047 parcel.writeLong(creationTime); 3048 if (mSmallIcon == null && icon != 0) { 3049 // you snuck an icon in here without using the builder; let's try to keep it 3050 mSmallIcon = Icon.createWithResource("", icon); 3051 } 3052 if (mSmallIcon != null) { 3053 parcel.writeInt(1); 3054 mSmallIcon.writeToParcel(parcel, 0); 3055 } else { 3056 parcel.writeInt(0); 3057 } 3058 parcel.writeInt(number); 3059 if (contentIntent != null) { 3060 parcel.writeInt(1); 3061 contentIntent.writeToParcel(parcel, 0); 3062 } else { 3063 parcel.writeInt(0); 3064 } 3065 if (deleteIntent != null) { 3066 parcel.writeInt(1); 3067 deleteIntent.writeToParcel(parcel, 0); 3068 } else { 3069 parcel.writeInt(0); 3070 } 3071 if (tickerText != null) { 3072 parcel.writeInt(1); 3073 TextUtils.writeToParcel(tickerText, parcel, flags); 3074 } else { 3075 parcel.writeInt(0); 3076 } 3077 if (tickerView != null) { 3078 parcel.writeInt(1); 3079 tickerView.writeToParcel(parcel, 0); 3080 } else { 3081 parcel.writeInt(0); 3082 } 3083 if (contentView != null) { 3084 parcel.writeInt(1); 3085 contentView.writeToParcel(parcel, 0); 3086 } else { 3087 parcel.writeInt(0); 3088 } 3089 if (mLargeIcon == null && largeIcon != null) { 3090 // you snuck an icon in here without using the builder; let's try to keep it 3091 mLargeIcon = Icon.createWithBitmap(largeIcon); 3092 } 3093 if (mLargeIcon != null) { 3094 parcel.writeInt(1); 3095 mLargeIcon.writeToParcel(parcel, 0); 3096 } else { 3097 parcel.writeInt(0); 3098 } 3099 3100 parcel.writeInt(defaults); 3101 parcel.writeInt(this.flags); 3102 3103 if (sound != null) { 3104 parcel.writeInt(1); 3105 sound.writeToParcel(parcel, 0); 3106 } else { 3107 parcel.writeInt(0); 3108 } 3109 parcel.writeInt(audioStreamType); 3110 3111 if (audioAttributes != null) { 3112 parcel.writeInt(1); 3113 audioAttributes.writeToParcel(parcel, 0); 3114 } else { 3115 parcel.writeInt(0); 3116 } 3117 3118 parcel.writeLongArray(vibrate); 3119 parcel.writeInt(ledARGB); 3120 parcel.writeInt(ledOnMS); 3121 parcel.writeInt(ledOffMS); 3122 parcel.writeInt(iconLevel); 3123 3124 if (fullScreenIntent != null) { 3125 parcel.writeInt(1); 3126 fullScreenIntent.writeToParcel(parcel, 0); 3127 } else { 3128 parcel.writeInt(0); 3129 } 3130 3131 parcel.writeInt(priority); 3132 3133 parcel.writeString8(category); 3134 3135 parcel.writeString8(mGroupKey); 3136 3137 parcel.writeString8(mSortKey); 3138 3139 parcel.writeBundle(extras); // null ok 3140 3141 parcel.writeTypedArray(actions, 0); // null ok 3142 3143 if (bigContentView != null) { 3144 parcel.writeInt(1); 3145 bigContentView.writeToParcel(parcel, 0); 3146 } else { 3147 parcel.writeInt(0); 3148 } 3149 3150 if (headsUpContentView != null) { 3151 parcel.writeInt(1); 3152 headsUpContentView.writeToParcel(parcel, 0); 3153 } else { 3154 parcel.writeInt(0); 3155 } 3156 3157 parcel.writeInt(visibility); 3158 3159 if (publicVersion != null) { 3160 parcel.writeInt(1); 3161 publicVersion.writeToParcel(parcel, 0); 3162 } else { 3163 parcel.writeInt(0); 3164 } 3165 3166 parcel.writeInt(color); 3167 3168 if (mChannelId != null) { 3169 parcel.writeInt(1); 3170 parcel.writeString8(mChannelId); 3171 } else { 3172 parcel.writeInt(0); 3173 } 3174 parcel.writeLong(mTimeout); 3175 3176 if (mShortcutId != null) { 3177 parcel.writeInt(1); 3178 parcel.writeString8(mShortcutId); 3179 } else { 3180 parcel.writeInt(0); 3181 } 3182 3183 if (mLocusId != null) { 3184 parcel.writeInt(1); 3185 mLocusId.writeToParcel(parcel, 0); 3186 } else { 3187 parcel.writeInt(0); 3188 } 3189 3190 parcel.writeInt(mBadgeIcon); 3191 3192 if (mSettingsText != null) { 3193 parcel.writeInt(1); 3194 TextUtils.writeToParcel(mSettingsText, parcel, flags); 3195 } else { 3196 parcel.writeInt(0); 3197 } 3198 3199 parcel.writeInt(mGroupAlertBehavior); 3200 3201 if (mBubbleMetadata != null) { 3202 parcel.writeInt(1); 3203 mBubbleMetadata.writeToParcel(parcel, 0); 3204 } else { 3205 parcel.writeInt(0); 3206 } 3207 3208 parcel.writeBoolean(mAllowSystemGeneratedContextualActions); 3209 3210 parcel.writeInt(mFgsDeferBehavior); 3211 3212 // mUsesStandardHeader is not written because it should be recomputed in listeners 3213 } 3214 3215 /** 3216 * Parcelable.Creator that instantiates Notification objects 3217 */ 3218 public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR 3219 = new Parcelable.Creator<Notification>() 3220 { 3221 public Notification createFromParcel(Parcel parcel) 3222 { 3223 return new Notification(parcel); 3224 } 3225 3226 public Notification[] newArray(int size) 3227 { 3228 return new Notification[size]; 3229 } 3230 }; 3231 3232 /** 3233 * @hide 3234 */ areActionsVisiblyDifferent(Notification first, Notification second)3235 public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { 3236 Notification.Action[] firstAs = first.actions; 3237 Notification.Action[] secondAs = second.actions; 3238 if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { 3239 return true; 3240 } 3241 if (firstAs != null && secondAs != null) { 3242 if (firstAs.length != secondAs.length) { 3243 return true; 3244 } 3245 for (int i = 0; i < firstAs.length; i++) { 3246 if (!Objects.equals(String.valueOf(firstAs[i].title), 3247 String.valueOf(secondAs[i].title))) { 3248 return true; 3249 } 3250 RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); 3251 RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); 3252 if (firstRs == null) { 3253 firstRs = new RemoteInput[0]; 3254 } 3255 if (secondRs == null) { 3256 secondRs = new RemoteInput[0]; 3257 } 3258 if (firstRs.length != secondRs.length) { 3259 return true; 3260 } 3261 for (int j = 0; j < firstRs.length; j++) { 3262 if (!Objects.equals(String.valueOf(firstRs[j].getLabel()), 3263 String.valueOf(secondRs[j].getLabel()))) { 3264 return true; 3265 } 3266 } 3267 } 3268 } 3269 return false; 3270 } 3271 3272 /** 3273 * @hide 3274 */ areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3275 public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { 3276 if (first.getStyle() == null) { 3277 return second.getStyle() != null; 3278 } 3279 if (second.getStyle() == null) { 3280 return true; 3281 } 3282 return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); 3283 } 3284 3285 /** 3286 * @hide 3287 */ areRemoteViewsChanged(Builder first, Builder second)3288 public static boolean areRemoteViewsChanged(Builder first, Builder second) { 3289 if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) { 3290 return true; 3291 } 3292 3293 if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) { 3294 return true; 3295 } 3296 if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) { 3297 return true; 3298 } 3299 if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) { 3300 return true; 3301 } 3302 3303 return false; 3304 } 3305 areRemoteViewsChanged(RemoteViews first, RemoteViews second)3306 private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) { 3307 if (first == null && second == null) { 3308 return false; 3309 } 3310 if (first == null && second != null || first != null && second == null) { 3311 return true; 3312 } 3313 3314 if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) { 3315 return true; 3316 } 3317 3318 if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) { 3319 return true; 3320 } 3321 3322 return false; 3323 } 3324 3325 /** 3326 * Parcelling creates multiple copies of objects in {@code extras}. Fix them. 3327 * <p> 3328 * For backwards compatibility {@code extras} holds some references to "real" member data such 3329 * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly 3330 * fine as long as the object stays in one process. 3331 * <p> 3332 * However, once the notification goes into a parcel each reference gets marshalled separately, 3333 * wasting memory. Especially with large images on Auto and TV, this is worth fixing. 3334 */ fixDuplicateExtras()3335 private void fixDuplicateExtras() { 3336 if (extras != null) { 3337 fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); 3338 } 3339 } 3340 3341 /** 3342 * If we find an extra that's exactly the same as one of the "real" fields but refers to a 3343 * separate object, replace it with the field's version to avoid holding duplicate copies. 3344 */ fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3345 private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { 3346 if (original != null && extras.getParcelable(extraName) != null) { 3347 extras.putParcelable(extraName, original); 3348 } 3349 } 3350 3351 /** 3352 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 3353 * layout. 3354 * 3355 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 3356 * in the view.</p> 3357 * @param context The context for your application / activity. 3358 * @param contentTitle The title that goes in the expanded entry. 3359 * @param contentText The text that goes in the expanded entry. 3360 * @param contentIntent The intent to launch when the user clicks the expanded notification. 3361 * If this is an activity, it must include the 3362 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 3363 * that you take care of task management as described in the 3364 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 3365 * Stack</a> document. 3366 * 3367 * @deprecated Use {@link Builder} instead. 3368 * @removed 3369 */ 3370 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3371 public void setLatestEventInfo(Context context, 3372 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 3373 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 3374 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 3375 new Throwable()); 3376 } 3377 3378 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3379 extras.putBoolean(EXTRA_SHOW_WHEN, true); 3380 } 3381 3382 // ensure that any information already set directly is preserved 3383 final Notification.Builder builder = new Notification.Builder(context, this); 3384 3385 // now apply the latestEventInfo fields 3386 if (contentTitle != null) { 3387 builder.setContentTitle(contentTitle); 3388 } 3389 if (contentText != null) { 3390 builder.setContentText(contentText); 3391 } 3392 builder.setContentIntent(contentIntent); 3393 3394 builder.build(); // callers expect this notification to be ready to use 3395 } 3396 3397 /** 3398 * Sets the token used for background operations for the pending intents associated with this 3399 * notification. 3400 * 3401 * This token is automatically set during deserialization for you, you usually won't need to 3402 * call this unless you want to change the existing token, if any. 3403 * 3404 * @hide 3405 */ setAllowlistToken(@ullable IBinder token)3406 public void setAllowlistToken(@Nullable IBinder token) { 3407 mAllowlistToken = token; 3408 } 3409 3410 /** 3411 * @hide 3412 */ addFieldsFromContext(Context context, Notification notification)3413 public static void addFieldsFromContext(Context context, Notification notification) { 3414 addFieldsFromContext(context.getApplicationInfo(), notification); 3415 } 3416 3417 /** 3418 * @hide 3419 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)3420 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 3421 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 3422 } 3423 3424 /** 3425 * @hide 3426 */ dumpDebug(ProtoOutputStream proto, long fieldId)3427 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 3428 long token = proto.start(fieldId); 3429 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 3430 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 3431 proto.write(NotificationProto.FLAGS, this.flags); 3432 proto.write(NotificationProto.COLOR, this.color); 3433 proto.write(NotificationProto.CATEGORY, this.category); 3434 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 3435 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 3436 if (this.actions != null) { 3437 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 3438 } 3439 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 3440 proto.write(NotificationProto.VISIBILITY, this.visibility); 3441 } 3442 if (publicVersion != null) { 3443 publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION); 3444 } 3445 proto.end(token); 3446 } 3447 3448 @Override toString()3449 public String toString() { 3450 StringBuilder sb = new StringBuilder(); 3451 sb.append("Notification(channel="); 3452 sb.append(getChannelId()); 3453 sb.append(" shortcut="); 3454 sb.append(getShortcutId()); 3455 sb.append(" contentView="); 3456 if (contentView != null) { 3457 sb.append(contentView.getPackage()); 3458 sb.append("/0x"); 3459 sb.append(Integer.toHexString(contentView.getLayoutId())); 3460 } else { 3461 sb.append("null"); 3462 } 3463 sb.append(" vibrate="); 3464 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3465 sb.append("default"); 3466 } else if (this.vibrate != null) { 3467 int N = this.vibrate.length-1; 3468 sb.append("["); 3469 for (int i=0; i<N; i++) { 3470 sb.append(this.vibrate[i]); 3471 sb.append(','); 3472 } 3473 if (N != -1) { 3474 sb.append(this.vibrate[N]); 3475 } 3476 sb.append("]"); 3477 } else { 3478 sb.append("null"); 3479 } 3480 sb.append(" sound="); 3481 if ((this.defaults & DEFAULT_SOUND) != 0) { 3482 sb.append("default"); 3483 } else if (this.sound != null) { 3484 sb.append(this.sound.toString()); 3485 } else { 3486 sb.append("null"); 3487 } 3488 if (this.tickerText != null) { 3489 sb.append(" tick"); 3490 } 3491 sb.append(" defaults=0x"); 3492 sb.append(Integer.toHexString(this.defaults)); 3493 sb.append(" flags=0x"); 3494 sb.append(Integer.toHexString(this.flags)); 3495 sb.append(String.format(" color=0x%08x", this.color)); 3496 if (this.category != null) { 3497 sb.append(" category="); 3498 sb.append(this.category); 3499 } 3500 if (this.mGroupKey != null) { 3501 sb.append(" groupKey="); 3502 sb.append(this.mGroupKey); 3503 } 3504 if (this.mSortKey != null) { 3505 sb.append(" sortKey="); 3506 sb.append(this.mSortKey); 3507 } 3508 if (actions != null) { 3509 sb.append(" actions="); 3510 sb.append(actions.length); 3511 } 3512 sb.append(" vis="); 3513 sb.append(visibilityToString(this.visibility)); 3514 if (this.publicVersion != null) { 3515 sb.append(" publicVersion="); 3516 sb.append(publicVersion.toString()); 3517 } 3518 if (this.mLocusId != null) { 3519 sb.append(" locusId="); 3520 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3521 } 3522 sb.append(")"); 3523 return sb.toString(); 3524 } 3525 3526 /** 3527 * {@hide} 3528 */ visibilityToString(int vis)3529 public static String visibilityToString(int vis) { 3530 switch (vis) { 3531 case VISIBILITY_PRIVATE: 3532 return "PRIVATE"; 3533 case VISIBILITY_PUBLIC: 3534 return "PUBLIC"; 3535 case VISIBILITY_SECRET: 3536 return "SECRET"; 3537 default: 3538 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3539 } 3540 } 3541 3542 /** 3543 * {@hide} 3544 */ priorityToString(@riority int pri)3545 public static String priorityToString(@Priority int pri) { 3546 switch (pri) { 3547 case PRIORITY_MIN: 3548 return "MIN"; 3549 case PRIORITY_LOW: 3550 return "LOW"; 3551 case PRIORITY_DEFAULT: 3552 return "DEFAULT"; 3553 case PRIORITY_HIGH: 3554 return "HIGH"; 3555 case PRIORITY_MAX: 3556 return "MAX"; 3557 default: 3558 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3559 } 3560 } 3561 3562 /** 3563 * @hide 3564 */ hasCompletedProgress()3565 public boolean hasCompletedProgress() { 3566 // not a progress notification; can't be complete 3567 if (!extras.containsKey(EXTRA_PROGRESS) 3568 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 3569 return false; 3570 } 3571 // many apps use max 0 for 'indeterminate'; not complete 3572 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 3573 return false; 3574 } 3575 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 3576 } 3577 3578 /** @removed */ 3579 @Deprecated getChannel()3580 public String getChannel() { 3581 return mChannelId; 3582 } 3583 3584 /** 3585 * Returns the id of the channel this notification posts to. 3586 */ getChannelId()3587 public String getChannelId() { 3588 return mChannelId; 3589 } 3590 3591 /** @removed */ 3592 @Deprecated getTimeout()3593 public long getTimeout() { 3594 return mTimeout; 3595 } 3596 3597 /** 3598 * Returns the duration from posting after which this notification should be canceled by the 3599 * system, if it's not canceled already. 3600 */ getTimeoutAfter()3601 public long getTimeoutAfter() { 3602 return mTimeout; 3603 } 3604 3605 /** 3606 * Returns what icon should be shown for this notification if it is being displayed in a 3607 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 3608 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 3609 */ getBadgeIconType()3610 public int getBadgeIconType() { 3611 return mBadgeIcon; 3612 } 3613 3614 /** 3615 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 3616 * 3617 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 3618 * notifications. 3619 */ getShortcutId()3620 public String getShortcutId() { 3621 return mShortcutId; 3622 } 3623 3624 /** 3625 * Gets the {@link LocusId} associated with this notification. 3626 * 3627 * <p>Used by the device's intelligence services to correlate objects (such as 3628 * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated. 3629 */ 3630 @Nullable getLocusId()3631 public LocusId getLocusId() { 3632 return mLocusId; 3633 } 3634 3635 /** 3636 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 3637 */ getSettingsText()3638 public CharSequence getSettingsText() { 3639 return mSettingsText; 3640 } 3641 3642 /** 3643 * Returns which type of notifications in a group are responsible for audibly alerting the 3644 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 3645 * {@link #GROUP_ALERT_SUMMARY}. 3646 */ getGroupAlertBehavior()3647 public @GroupAlertBehavior int getGroupAlertBehavior() { 3648 return mGroupAlertBehavior; 3649 } 3650 3651 /** 3652 * Returns the bubble metadata that will be used to display app content in a floating window 3653 * over the existing foreground activity. 3654 */ 3655 @Nullable getBubbleMetadata()3656 public BubbleMetadata getBubbleMetadata() { 3657 return mBubbleMetadata; 3658 } 3659 3660 /** 3661 * Sets the {@link BubbleMetadata} for this notification. 3662 * @hide 3663 */ setBubbleMetadata(BubbleMetadata data)3664 public void setBubbleMetadata(BubbleMetadata data) { 3665 mBubbleMetadata = data; 3666 } 3667 3668 /** 3669 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 3670 * for this notification. 3671 */ getAllowSystemGeneratedContextualActions()3672 public boolean getAllowSystemGeneratedContextualActions() { 3673 return mAllowSystemGeneratedContextualActions; 3674 } 3675 3676 /** 3677 * The small icon representing this notification in the status bar and content view. 3678 * 3679 * @return the small icon representing this notification. 3680 * 3681 * @see Builder#getSmallIcon() 3682 * @see Builder#setSmallIcon(Icon) 3683 */ getSmallIcon()3684 public Icon getSmallIcon() { 3685 return mSmallIcon; 3686 } 3687 3688 /** 3689 * Used when notifying to clean up legacy small icons. 3690 * @hide 3691 */ 3692 @UnsupportedAppUsage setSmallIcon(Icon icon)3693 public void setSmallIcon(Icon icon) { 3694 mSmallIcon = icon; 3695 } 3696 3697 /** 3698 * The large icon shown in this notification's content view. 3699 * @see Builder#getLargeIcon() 3700 * @see Builder#setLargeIcon(Icon) 3701 */ getLargeIcon()3702 public Icon getLargeIcon() { 3703 return mLargeIcon; 3704 } 3705 3706 /** 3707 * @hide 3708 */ 3709 @UnsupportedAppUsage isGroupSummary()3710 public boolean isGroupSummary() { 3711 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 3712 } 3713 3714 /** 3715 * @hide 3716 */ 3717 @UnsupportedAppUsage isGroupChild()3718 public boolean isGroupChild() { 3719 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 3720 } 3721 3722 /** 3723 * @hide 3724 */ suppressAlertingDueToGrouping()3725 public boolean suppressAlertingDueToGrouping() { 3726 if (isGroupSummary() 3727 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 3728 return true; 3729 } else if (isGroupChild() 3730 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 3731 return true; 3732 } 3733 return false; 3734 } 3735 3736 3737 /** 3738 * Finds and returns a remote input and its corresponding action. 3739 * 3740 * @param requiresFreeform requires the remoteinput to allow freeform or not. 3741 * @return the result pair, {@code null} if no result is found. 3742 */ 3743 @Nullable findRemoteInputActionPair(boolean requiresFreeform)3744 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 3745 if (actions == null) { 3746 return null; 3747 } 3748 for (Notification.Action action : actions) { 3749 if (action.getRemoteInputs() == null) { 3750 continue; 3751 } 3752 RemoteInput resultRemoteInput = null; 3753 for (RemoteInput remoteInput : action.getRemoteInputs()) { 3754 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 3755 resultRemoteInput = remoteInput; 3756 } 3757 } 3758 if (resultRemoteInput != null) { 3759 return Pair.create(resultRemoteInput, action); 3760 } 3761 } 3762 return null; 3763 } 3764 3765 /** 3766 * Returns the actions that are contextual (that is, suggested because of the content of the 3767 * notification) out of the actions in this notification. 3768 */ getContextualActions()3769 public @NonNull List<Notification.Action> getContextualActions() { 3770 if (actions == null) return Collections.emptyList(); 3771 3772 List<Notification.Action> contextualActions = new ArrayList<>(); 3773 for (Notification.Action action : actions) { 3774 if (action.isContextual()) { 3775 contextualActions.add(action); 3776 } 3777 } 3778 return contextualActions; 3779 } 3780 3781 /** 3782 * Builder class for {@link Notification} objects. 3783 * 3784 * Provides a convenient way to set the various fields of a {@link Notification} and generate 3785 * content views using the platform's notification layout template. If your app supports 3786 * versions of Android as old as API level 4, you can instead use 3787 * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder}, 3788 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 3789 * library</a>. 3790 * 3791 * <p>Example: 3792 * 3793 * <pre class="prettyprint"> 3794 * Notification noti = new Notification.Builder(mContext) 3795 * .setContentTitle("New mail from " + sender.toString()) 3796 * .setContentText(subject) 3797 * .setSmallIcon(R.drawable.new_mail) 3798 * .setLargeIcon(aBitmap) 3799 * .build(); 3800 * </pre> 3801 */ 3802 public static class Builder { 3803 /** 3804 * @hide 3805 */ 3806 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 3807 "android.rebuild.contentViewActionCount"; 3808 /** 3809 * @hide 3810 */ 3811 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 3812 = "android.rebuild.bigViewActionCount"; 3813 /** 3814 * @hide 3815 */ 3816 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 3817 = "android.rebuild.hudViewActionCount"; 3818 3819 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 3820 SystemProperties.getBoolean("notifications.only_title", true); 3821 3822 /** 3823 * The lightness difference that has to be added to the primary text color to obtain the 3824 * secondary text color when the background is light. 3825 */ 3826 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 3827 3828 /** 3829 * The lightness difference that has to be added to the primary text color to obtain the 3830 * secondary text color when the background is dark. 3831 * A bit less then the above value, since it looks better on dark backgrounds. 3832 */ 3833 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 3834 3835 private Context mContext; 3836 private Notification mN; 3837 private Bundle mUserExtras = new Bundle(); 3838 private Style mStyle; 3839 @UnsupportedAppUsage 3840 private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS); 3841 private ArrayList<Person> mPersonList = new ArrayList<>(); 3842 private ContrastColorUtil mColorUtil; 3843 private boolean mIsLegacy; 3844 private boolean mIsLegacyInitialized; 3845 3846 /** 3847 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 3848 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 3849 */ 3850 StandardTemplateParams mParams = new StandardTemplateParams(); 3851 Colors mColors = new Colors(); 3852 3853 private boolean mTintActionButtons; 3854 private boolean mInNightMode; 3855 3856 /** 3857 * Constructs a new Builder with the defaults: 3858 * 3859 * @param context 3860 * A {@link Context} that will be used by the Builder to construct the 3861 * RemoteViews. The Context will not be held past the lifetime of this Builder 3862 * object. 3863 * @param channelId 3864 * The constructed Notification will be posted on this 3865 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 3866 * created using {@link NotificationManager#createNotificationChannel}. 3867 */ Builder(Context context, String channelId)3868 public Builder(Context context, String channelId) { 3869 this(context, (Notification) null); 3870 mN.mChannelId = channelId; 3871 } 3872 3873 /** 3874 * @deprecated use {@link #Builder(Context, String)} 3875 * instead. All posted Notifications must specify a NotificationChannel Id. 3876 */ 3877 @Deprecated Builder(Context context)3878 public Builder(Context context) { 3879 this(context, (Notification) null); 3880 } 3881 3882 /** 3883 * @hide 3884 */ Builder(Context context, Notification toAdopt)3885 public Builder(Context context, Notification toAdopt) { 3886 mContext = context; 3887 Resources res = mContext.getResources(); 3888 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 3889 3890 if (res.getBoolean(R.bool.config_enableNightMode)) { 3891 Configuration currentConfig = res.getConfiguration(); 3892 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 3893 == Configuration.UI_MODE_NIGHT_YES; 3894 } 3895 3896 if (toAdopt == null) { 3897 mN = new Notification(); 3898 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3899 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 3900 } 3901 mN.priority = PRIORITY_DEFAULT; 3902 mN.visibility = VISIBILITY_PRIVATE; 3903 } else { 3904 mN = toAdopt; 3905 if (mN.actions != null) { 3906 Collections.addAll(mActions, mN.actions); 3907 } 3908 3909 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 3910 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST); 3911 mPersonList.addAll(people); 3912 } 3913 3914 if (mN.getSmallIcon() == null && mN.icon != 0) { 3915 setSmallIcon(mN.icon); 3916 } 3917 3918 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 3919 setLargeIcon(mN.largeIcon); 3920 } 3921 3922 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 3923 if (!TextUtils.isEmpty(templateClass)) { 3924 final Class<? extends Style> styleClass 3925 = getNotificationStyleClass(templateClass); 3926 if (styleClass == null) { 3927 Log.d(TAG, "Unknown style class: " + templateClass); 3928 } else { 3929 try { 3930 final Constructor<? extends Style> ctor = 3931 styleClass.getDeclaredConstructor(); 3932 ctor.setAccessible(true); 3933 final Style style = ctor.newInstance(); 3934 style.restoreFromExtras(mN.extras); 3935 3936 if (style != null) { 3937 setStyle(style); 3938 } 3939 } catch (Throwable t) { 3940 Log.e(TAG, "Could not create Style", t); 3941 } 3942 } 3943 } 3944 } 3945 } 3946 getColorUtil()3947 private ContrastColorUtil getColorUtil() { 3948 if (mColorUtil == null) { 3949 mColorUtil = ContrastColorUtil.getInstance(mContext); 3950 } 3951 return mColorUtil; 3952 } 3953 3954 /** 3955 * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that 3956 * use this method to link to a published long-lived sharing shortcut may appear in a 3957 * dedicated Conversation section of the shade and may show configuration options that 3958 * are unique to conversations. This behavior should be reserved for person to person(s) 3959 * conversations where there is a likely social obligation for an individual to respond. 3960 * <p> 3961 * For example, the following are some examples of notifications that belong in the 3962 * conversation space: 3963 * <ul> 3964 * <li>1:1 conversations between two individuals</li> 3965 * <li>Group conversations between individuals where everyone can contribute</li> 3966 * </ul> 3967 * And the following are some examples of notifications that do not belong in the 3968 * conversation space: 3969 * <ul> 3970 * <li>Advertisements from a bot (even if personal and contextualized)</li> 3971 * <li>Engagement notifications from a bot</li> 3972 * <li>Directional conversations where there is an active speaker and many passive 3973 * individuals</li> 3974 * <li>Stream / posting updates from other individuals</li> 3975 * <li>Email, document comments, or other conversation types that are not real-time</li> 3976 * </ul> 3977 * </p> 3978 * 3979 * <p> 3980 * Additionally, this method can be used for all types of notifications to mark this 3981 * notification as duplicative of a Launcher shortcut. Launchers that show badges or 3982 * notification content may then suppress the shortcut in favor of the content of this 3983 * notification. 3984 * <p> 3985 * If this notification has {@link BubbleMetadata} attached that was created with 3986 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 3987 * metadata matches the shortcutId set here, if one was set. If the shortcutId's were 3988 * specified but do not match, an exception is thrown. 3989 * 3990 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 3991 * is linked to 3992 * 3993 * @see BubbleMetadata.Builder#Builder(String) 3994 */ 3995 @NonNull setShortcutId(String shortcutId)3996 public Builder setShortcutId(String shortcutId) { 3997 mN.mShortcutId = shortcutId; 3998 return this; 3999 } 4000 4001 /** 4002 * Sets the {@link LocusId} associated with this notification. 4003 * 4004 * <p>This method should be called when the {@link LocusId} is used in other places (such 4005 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence 4006 * services can correlate them. 4007 */ 4008 @NonNull setLocusId(@ullable LocusId locusId)4009 public Builder setLocusId(@Nullable LocusId locusId) { 4010 mN.mLocusId = locusId; 4011 return this; 4012 } 4013 4014 /** 4015 * Sets which icon to display as a badge for this notification. 4016 * 4017 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 4018 * {@link #BADGE_ICON_LARGE}. 4019 * 4020 * Note: This value might be ignored, for launchers that don't support badge icons. 4021 */ 4022 @NonNull setBadgeIconType(int icon)4023 public Builder setBadgeIconType(int icon) { 4024 mN.mBadgeIcon = icon; 4025 return this; 4026 } 4027 4028 /** 4029 * Sets the group alert behavior for this notification. Use this method to mute this 4030 * notification if alerts for this notification's group should be handled by a different 4031 * notification. This is only applicable for notifications that belong to a 4032 * {@link #setGroup(String) group}. This must be called on all notifications you want to 4033 * mute. For example, if you want only the summary of your group to make noise, all 4034 * children in the group should have the group alert behavior {@link #GROUP_ALERT_SUMMARY}. 4035 * 4036 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 4037 */ 4038 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4039 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 4040 mN.mGroupAlertBehavior = groupAlertBehavior; 4041 return this; 4042 } 4043 4044 /** 4045 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 4046 * window over the existing foreground activity. 4047 * 4048 * <p>This data will be ignored unless the notification is posted to a channel that 4049 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 4050 * 4051 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 4052 * collapsed state outside of the notification shade on unlocked devices. When a user 4053 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 4054 */ 4055 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)4056 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 4057 mN.mBubbleMetadata = data; 4058 return this; 4059 } 4060 4061 /** @removed */ 4062 @Deprecated setChannel(String channelId)4063 public Builder setChannel(String channelId) { 4064 mN.mChannelId = channelId; 4065 return this; 4066 } 4067 4068 /** 4069 * Specifies the channel the notification should be delivered on. 4070 */ 4071 @NonNull setChannelId(String channelId)4072 public Builder setChannelId(String channelId) { 4073 mN.mChannelId = channelId; 4074 return this; 4075 } 4076 4077 /** @removed */ 4078 @Deprecated setTimeout(long durationMs)4079 public Builder setTimeout(long durationMs) { 4080 mN.mTimeout = durationMs; 4081 return this; 4082 } 4083 4084 /** 4085 * Specifies a duration in milliseconds after which this notification should be canceled, 4086 * if it is not already canceled. 4087 */ 4088 @NonNull setTimeoutAfter(long durationMs)4089 public Builder setTimeoutAfter(long durationMs) { 4090 mN.mTimeout = durationMs; 4091 return this; 4092 } 4093 4094 /** 4095 * Add a timestamp pertaining to the notification (usually the time the event occurred). 4096 * 4097 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not 4098 * shown anymore by default and must be opted into by using 4099 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 4100 * 4101 * @see Notification#when 4102 */ 4103 @NonNull setWhen(long when)4104 public Builder setWhen(long when) { 4105 mN.when = when; 4106 return this; 4107 } 4108 4109 /** 4110 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 4111 * in the content view. 4112 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this defaults to 4113 * {@code false}. For earlier apps, the default is {@code true}. 4114 */ 4115 @NonNull setShowWhen(boolean show)4116 public Builder setShowWhen(boolean show) { 4117 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 4118 return this; 4119 } 4120 4121 /** 4122 * Show the {@link Notification#when} field as a stopwatch. 4123 * 4124 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 4125 * automatically updating display of the minutes and seconds since <code>when</code>. 4126 * 4127 * Useful when showing an elapsed time (like an ongoing phone call). 4128 * 4129 * The counter can also be set to count down to <code>when</code> when using 4130 * {@link #setChronometerCountDown(boolean)}. 4131 * 4132 * @see android.widget.Chronometer 4133 * @see Notification#when 4134 * @see #setChronometerCountDown(boolean) 4135 */ 4136 @NonNull setUsesChronometer(boolean b)4137 public Builder setUsesChronometer(boolean b) { 4138 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 4139 return this; 4140 } 4141 4142 /** 4143 * Sets the Chronometer to count down instead of counting up. 4144 * 4145 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 4146 * If it isn't set the chronometer will count up. 4147 * 4148 * @see #setUsesChronometer(boolean) 4149 */ 4150 @NonNull setChronometerCountDown(boolean countDown)4151 public Builder setChronometerCountDown(boolean countDown) { 4152 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 4153 return this; 4154 } 4155 4156 /** 4157 * Set the small icon resource, which will be used to represent the notification in the 4158 * status bar. 4159 * 4160 4161 * The platform template for the expanded view will draw this icon in the left, unless a 4162 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 4163 * icon will be moved to the right-hand side. 4164 * 4165 4166 * @param icon 4167 * A resource ID in the application's package of the drawable to use. 4168 * @see Notification#icon 4169 */ 4170 @NonNull setSmallIcon(@rawableRes int icon)4171 public Builder setSmallIcon(@DrawableRes int icon) { 4172 return setSmallIcon(icon != 0 4173 ? Icon.createWithResource(mContext, icon) 4174 : null); 4175 } 4176 4177 /** 4178 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 4179 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 4180 * LevelListDrawable}. 4181 * 4182 * @param icon A resource ID in the application's package of the drawable to use. 4183 * @param level The level to use for the icon. 4184 * 4185 * @see Notification#icon 4186 * @see Notification#iconLevel 4187 */ 4188 @NonNull setSmallIcon(@rawableRes int icon, int level)4189 public Builder setSmallIcon(@DrawableRes int icon, int level) { 4190 mN.iconLevel = level; 4191 return setSmallIcon(icon); 4192 } 4193 4194 /** 4195 * Set the small icon, which will be used to represent the notification in the 4196 * status bar and content view (unless overridden there by a 4197 * {@link #setLargeIcon(Bitmap) large icon}). 4198 * 4199 * @param icon An Icon object to use. 4200 * @see Notification#icon 4201 */ 4202 @NonNull setSmallIcon(Icon icon)4203 public Builder setSmallIcon(Icon icon) { 4204 mN.setSmallIcon(icon); 4205 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 4206 mN.icon = icon.getResId(); 4207 } 4208 return this; 4209 } 4210 4211 /** 4212 * Set the first line of text in the platform notification template. 4213 */ 4214 @NonNull setContentTitle(CharSequence title)4215 public Builder setContentTitle(CharSequence title) { 4216 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 4217 return this; 4218 } 4219 4220 /** 4221 * Set the second line of text in the platform notification template. 4222 */ 4223 @NonNull setContentText(CharSequence text)4224 public Builder setContentText(CharSequence text) { 4225 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 4226 return this; 4227 } 4228 4229 /** 4230 * This provides some additional information that is displayed in the notification. No 4231 * guarantees are given where exactly it is displayed. 4232 * 4233 * <p>This information should only be provided if it provides an essential 4234 * benefit to the understanding of the notification. The more text you provide the 4235 * less readable it becomes. For example, an email client should only provide the account 4236 * name here if more than one email account has been added.</p> 4237 * 4238 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 4239 * notification header area. 4240 * 4241 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 4242 * this will be shown in the third line of text in the platform notification template. 4243 * You should not be using {@link #setProgress(int, int, boolean)} at the 4244 * same time on those versions; they occupy the same place. 4245 * </p> 4246 */ 4247 @NonNull setSubText(CharSequence text)4248 public Builder setSubText(CharSequence text) { 4249 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 4250 return this; 4251 } 4252 4253 /** 4254 * Provides text that will appear as a link to your application's settings. 4255 * 4256 * <p>This text does not appear within notification {@link Style templates} but may 4257 * appear when the user uses an affordance to learn more about the notification. 4258 * Additionally, this text will not appear unless you provide a valid link target by 4259 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 4260 * 4261 * <p>This text is meant to be concise description about what the user can customize 4262 * when they click on this link. The recommended maximum length is 40 characters. 4263 * @param text 4264 * @return 4265 */ 4266 @NonNull setSettingsText(CharSequence text)4267 public Builder setSettingsText(CharSequence text) { 4268 mN.mSettingsText = safeCharSequence(text); 4269 return this; 4270 } 4271 4272 /** 4273 * Set the remote input history. 4274 * 4275 * This should be set to the most recent inputs that have been sent 4276 * through a {@link RemoteInput} of this Notification and cleared once the it is no 4277 * longer relevant (e.g. for chat notifications once the other party has responded). 4278 * 4279 * The most recent input must be stored at the 0 index, the second most recent at the 4280 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 4281 * and how much of each individual input is shown. 4282 * 4283 * <p>Note: The reply text will only be shown on notifications that have least one action 4284 * with a {@code RemoteInput}.</p> 4285 */ 4286 @NonNull setRemoteInputHistory(CharSequence[] text)4287 public Builder setRemoteInputHistory(CharSequence[] text) { 4288 if (text == null) { 4289 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 4290 } else { 4291 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); 4292 CharSequence[] safe = new CharSequence[itemCount]; 4293 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; 4294 for (int i = 0; i < itemCount; i++) { 4295 safe[i] = safeCharSequence(text[i]); 4296 items[i] = new RemoteInputHistoryItem(text[i]); 4297 } 4298 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 4299 4300 // Also add these messages as structured history items. 4301 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); 4302 } 4303 return this; 4304 } 4305 4306 /** 4307 * Set the remote input history, with support for embedding URIs and mime types for 4308 * images and other media. 4309 * @hide 4310 */ 4311 @NonNull setRemoteInputHistory(RemoteInputHistoryItem[] items)4312 public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { 4313 if (items == null) { 4314 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); 4315 } else { 4316 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); 4317 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; 4318 for (int i = 0; i < itemCount; i++) { 4319 history[i] = items[i]; 4320 } 4321 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); 4322 } 4323 return this; 4324 } 4325 4326 /** 4327 * Sets whether remote history entries view should have a spinner. 4328 * @hide 4329 */ 4330 @NonNull setShowRemoteInputSpinner(boolean showSpinner)4331 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 4332 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 4333 return this; 4334 } 4335 4336 /** 4337 * Sets whether smart reply buttons should be hidden. 4338 * @hide 4339 */ 4340 @NonNull setHideSmartReplies(boolean hideSmartReplies)4341 public Builder setHideSmartReplies(boolean hideSmartReplies) { 4342 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 4343 return this; 4344 } 4345 4346 /** 4347 * Sets the number of items this notification represents. May be displayed as a badge count 4348 * for Launchers that support badging. 4349 */ 4350 @NonNull setNumber(int number)4351 public Builder setNumber(int number) { 4352 mN.number = number; 4353 return this; 4354 } 4355 4356 /** 4357 * A small piece of additional information pertaining to this notification. 4358 * 4359 * The platform template will draw this on the last line of the notification, at the far 4360 * right (to the right of a smallIcon if it has been placed there). 4361 * 4362 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 4363 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 4364 * field will still show up, but the subtext will take precedence. 4365 */ 4366 @Deprecated setContentInfo(CharSequence info)4367 public Builder setContentInfo(CharSequence info) { 4368 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 4369 return this; 4370 } 4371 4372 /** 4373 * Set the progress this notification represents. 4374 * 4375 * The platform template will represent this using a {@link ProgressBar}. 4376 */ 4377 @NonNull setProgress(int max, int progress, boolean indeterminate)4378 public Builder setProgress(int max, int progress, boolean indeterminate) { 4379 mN.extras.putInt(EXTRA_PROGRESS, progress); 4380 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 4381 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 4382 return this; 4383 } 4384 4385 /** 4386 * Supply a custom RemoteViews to use instead of the platform template. 4387 * 4388 * Use {@link #setCustomContentView(RemoteViews)} instead. 4389 */ 4390 @Deprecated setContent(RemoteViews views)4391 public Builder setContent(RemoteViews views) { 4392 return setCustomContentView(views); 4393 } 4394 4395 /** 4396 * Supply custom RemoteViews to use instead of the platform template. 4397 * 4398 * This will override the layout that would otherwise be constructed by this Builder 4399 * object. 4400 */ 4401 @NonNull setCustomContentView(RemoteViews contentView)4402 public Builder setCustomContentView(RemoteViews contentView) { 4403 mN.contentView = contentView; 4404 return this; 4405 } 4406 4407 /** 4408 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 4409 * 4410 * This will override the expanded layout that would otherwise be constructed by this 4411 * Builder object. 4412 */ 4413 @NonNull setCustomBigContentView(RemoteViews contentView)4414 public Builder setCustomBigContentView(RemoteViews contentView) { 4415 mN.bigContentView = contentView; 4416 return this; 4417 } 4418 4419 /** 4420 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 4421 * 4422 * This will override the heads-up layout that would otherwise be constructed by this 4423 * Builder object. 4424 */ 4425 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)4426 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 4427 mN.headsUpContentView = contentView; 4428 return this; 4429 } 4430 4431 /** 4432 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 4433 * 4434 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4435 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4436 * while processing broadcast receivers or services in response to notification clicks. To 4437 * launch an activity in those cases, provide a {@link PendingIntent} for the activity 4438 * itself. 4439 * 4440 * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 4441 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 4442 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 4443 * to assign PendingIntents to individual views in that custom layout (i.e., to create 4444 * clickable buttons inside the notification view). 4445 * 4446 * @see Notification#contentIntent Notification.contentIntent 4447 */ 4448 @NonNull setContentIntent(PendingIntent intent)4449 public Builder setContentIntent(PendingIntent intent) { 4450 mN.contentIntent = intent; 4451 return this; 4452 } 4453 4454 /** 4455 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 4456 * 4457 * @see Notification#deleteIntent 4458 */ 4459 @NonNull setDeleteIntent(PendingIntent intent)4460 public Builder setDeleteIntent(PendingIntent intent) { 4461 mN.deleteIntent = intent; 4462 return this; 4463 } 4464 4465 /** 4466 * An intent to launch instead of posting the notification to the status bar. 4467 * Only for use with extremely high-priority notifications demanding the user's 4468 * <strong>immediate</strong> attention, such as an incoming phone call or 4469 * alarm clock that the user has explicitly set to a particular time. 4470 * If this facility is used for something else, please give the user an option 4471 * to turn it off and use a normal notification, as this can be extremely 4472 * disruptive. 4473 * 4474 * <p> 4475 * The system UI may choose to display a heads-up notification, instead of 4476 * launching this intent, while the user is using the device. 4477 * </p> 4478 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 4479 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 4480 * use full screen intents.</p> 4481 * <p> 4482 * To be launched as a full screen intent, the notification must also be posted to a 4483 * channel with importance level set to IMPORTANCE_HIGH or higher. 4484 * </p> 4485 * 4486 * @param intent The pending intent to launch. 4487 * @param highPriority Passing true will cause this notification to be sent 4488 * even if other notifications are suppressed. 4489 * 4490 * @see Notification#fullScreenIntent 4491 */ 4492 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)4493 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 4494 mN.fullScreenIntent = intent; 4495 setFlag(FLAG_HIGH_PRIORITY, highPriority); 4496 return this; 4497 } 4498 4499 /** 4500 * Set the "ticker" text which is sent to accessibility services. 4501 * 4502 * @see Notification#tickerText 4503 */ 4504 @NonNull setTicker(CharSequence tickerText)4505 public Builder setTicker(CharSequence tickerText) { 4506 mN.tickerText = safeCharSequence(tickerText); 4507 return this; 4508 } 4509 4510 /** 4511 * Obsolete version of {@link #setTicker(CharSequence)}. 4512 * 4513 */ 4514 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)4515 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 4516 setTicker(tickerText); 4517 // views is ignored 4518 return this; 4519 } 4520 4521 /** 4522 * Add a large icon to the notification content view. 4523 * 4524 * In the platform template, this image will be shown either on the right of the 4525 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4526 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4527 */ 4528 @NonNull setLargeIcon(Bitmap b)4529 public Builder setLargeIcon(Bitmap b) { 4530 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 4531 } 4532 4533 /** 4534 * Add a large icon to the notification content view. 4535 * 4536 * In the platform template, this image will be shown either on the right of the 4537 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 4538 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 4539 */ 4540 @NonNull setLargeIcon(Icon icon)4541 public Builder setLargeIcon(Icon icon) { 4542 mN.mLargeIcon = icon; 4543 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 4544 return this; 4545 } 4546 4547 /** 4548 * Set the sound to play. 4549 * 4550 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 4551 * for notifications. 4552 * 4553 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4554 */ 4555 @Deprecated setSound(Uri sound)4556 public Builder setSound(Uri sound) { 4557 mN.sound = sound; 4558 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 4559 return this; 4560 } 4561 4562 /** 4563 * Set the sound to play, along with a specific stream on which to play it. 4564 * 4565 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 4566 * 4567 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 4568 */ 4569 @Deprecated setSound(Uri sound, int streamType)4570 public Builder setSound(Uri sound, int streamType) { 4571 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 4572 mN.sound = sound; 4573 mN.audioStreamType = streamType; 4574 return this; 4575 } 4576 4577 /** 4578 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 4579 * use during playback. 4580 * 4581 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4582 * @see Notification#sound 4583 */ 4584 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)4585 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 4586 mN.sound = sound; 4587 mN.audioAttributes = audioAttributes; 4588 return this; 4589 } 4590 4591 /** 4592 * Set the vibration pattern to use. 4593 * 4594 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 4595 * <code>pattern</code> parameter. 4596 * 4597 * <p> 4598 * A notification that vibrates is more likely to be presented as a heads-up notification. 4599 * </p> 4600 * 4601 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 4602 * @see Notification#vibrate 4603 */ 4604 @Deprecated setVibrate(long[] pattern)4605 public Builder setVibrate(long[] pattern) { 4606 mN.vibrate = pattern; 4607 return this; 4608 } 4609 4610 /** 4611 * Set the desired color for the indicator LED on the device, as well as the 4612 * blink duty cycle (specified in milliseconds). 4613 * 4614 4615 * Not all devices will honor all (or even any) of these values. 4616 * 4617 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 4618 * @see Notification#ledARGB 4619 * @see Notification#ledOnMS 4620 * @see Notification#ledOffMS 4621 */ 4622 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)4623 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 4624 mN.ledARGB = argb; 4625 mN.ledOnMS = onMs; 4626 mN.ledOffMS = offMs; 4627 if (onMs != 0 || offMs != 0) { 4628 mN.flags |= FLAG_SHOW_LIGHTS; 4629 } 4630 return this; 4631 } 4632 4633 /** 4634 * Set whether this is an "ongoing" notification. 4635 * 4636 4637 * Ongoing notifications cannot be dismissed by the user, so your application or service 4638 * must take care of canceling them. 4639 * 4640 4641 * They are typically used to indicate a background task that the user is actively engaged 4642 * with (e.g., playing music) or is pending in some way and therefore occupying the device 4643 * (e.g., a file download, sync operation, active network connection). 4644 * 4645 4646 * @see Notification#FLAG_ONGOING_EVENT 4647 */ 4648 @NonNull setOngoing(boolean ongoing)4649 public Builder setOngoing(boolean ongoing) { 4650 setFlag(FLAG_ONGOING_EVENT, ongoing); 4651 return this; 4652 } 4653 4654 /** 4655 * Set whether this notification should be colorized. When set, the color set with 4656 * {@link #setColor(int)} will be used as the background color of this notification. 4657 * <p> 4658 * This should only be used for high priority ongoing tasks like navigation, an ongoing 4659 * call, or other similarly high-priority events for the user. 4660 * <p> 4661 * For most styles, the coloring will only be applied if the notification is for a 4662 * foreground service notification. 4663 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 4664 * that have a media session attached there is no such requirement. 4665 * 4666 * @see #setColor(int) 4667 * @see MediaStyle#setMediaSession(MediaSession.Token) 4668 */ 4669 @NonNull setColorized(boolean colorize)4670 public Builder setColorized(boolean colorize) { 4671 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 4672 return this; 4673 } 4674 4675 /** 4676 * Set this flag if you would only like the sound, vibrate 4677 * and ticker to be played if the notification is not already showing. 4678 * 4679 * Note that using this flag will stop any ongoing alerting behaviour such 4680 * as sound, vibration or blinking notification LED. 4681 * 4682 * @see Notification#FLAG_ONLY_ALERT_ONCE 4683 */ 4684 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)4685 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 4686 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 4687 return this; 4688 } 4689 4690 /** 4691 * Specify a desired visibility policy for a Notification associated with a 4692 * foreground service. By default, the system can choose to defer 4693 * visibility of the notification for a short time after the service is 4694 * started. Pass 4695 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE} 4696 * to this method in order to guarantee that visibility is never deferred. Pass 4697 * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4698 * to request that visibility is deferred whenever possible. 4699 * 4700 * <p class="note">Note that deferred visibility is not guaranteed. There 4701 * may be some circumstances under which the system will show the foreground 4702 * service's associated Notification immediately even when the app has used 4703 * this method to explicitly request deferred display.</p> 4704 * @param behavior One of 4705 * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT}, 4706 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}, 4707 * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 4708 * @return 4709 */ 4710 @NonNull setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)4711 public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) { 4712 mN.mFgsDeferBehavior = behavior; 4713 return this; 4714 } 4715 4716 /** 4717 * Make this notification automatically dismissed when the user touches it. 4718 * 4719 * @see Notification#FLAG_AUTO_CANCEL 4720 */ 4721 @NonNull setAutoCancel(boolean autoCancel)4722 public Builder setAutoCancel(boolean autoCancel) { 4723 setFlag(FLAG_AUTO_CANCEL, autoCancel); 4724 return this; 4725 } 4726 4727 /** 4728 * Set whether or not this notification should not bridge to other devices. 4729 * 4730 * <p>Some notifications can be bridged to other devices for remote display. 4731 * This hint can be set to recommend this notification not be bridged. 4732 */ 4733 @NonNull setLocalOnly(boolean localOnly)4734 public Builder setLocalOnly(boolean localOnly) { 4735 setFlag(FLAG_LOCAL_ONLY, localOnly); 4736 return this; 4737 } 4738 4739 /** 4740 * Set which notification properties will be inherited from system defaults. 4741 * <p> 4742 * The value should be one or more of the following fields combined with 4743 * bitwise-or: 4744 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 4745 * <p> 4746 * For all default values, use {@link #DEFAULT_ALL}. 4747 * 4748 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 4749 * {@link NotificationChannel#enableLights(boolean)} and 4750 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 4751 */ 4752 @Deprecated setDefaults(int defaults)4753 public Builder setDefaults(int defaults) { 4754 mN.defaults = defaults; 4755 return this; 4756 } 4757 4758 /** 4759 * Set the priority of this notification. 4760 * 4761 * @see Notification#priority 4762 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 4763 */ 4764 @Deprecated setPriority(@riority int pri)4765 public Builder setPriority(@Priority int pri) { 4766 mN.priority = pri; 4767 return this; 4768 } 4769 4770 /** 4771 * Set the notification category. 4772 * 4773 * @see Notification#category 4774 */ 4775 @NonNull setCategory(String category)4776 public Builder setCategory(String category) { 4777 mN.category = category; 4778 return this; 4779 } 4780 4781 /** 4782 * Add a person that is relevant to this notification. 4783 * 4784 * <P> 4785 * Depending on user preferences, this annotation may allow the notification to pass 4786 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4787 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4788 * appear more prominently in the user interface. 4789 * </P> 4790 * 4791 * <P> 4792 * The person should be specified by the {@code String} representation of a 4793 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 4794 * </P> 4795 * 4796 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 4797 * URIs. The path part of these URIs must exist in the contacts database, in the 4798 * appropriate column, or the reference will be discarded as invalid. Telephone schema 4799 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 4800 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 4801 * identify a person without an entry in the contacts database. 4802 * </P> 4803 * 4804 * @param uri A URI for the person. 4805 * @see Notification#EXTRA_PEOPLE 4806 * @deprecated use {@link #addPerson(Person)} 4807 */ addPerson(String uri)4808 public Builder addPerson(String uri) { 4809 addPerson(new Person.Builder().setUri(uri).build()); 4810 return this; 4811 } 4812 4813 /** 4814 * Add a person that is relevant to this notification. 4815 * 4816 * <P> 4817 * Depending on user preferences, this annotation may allow the notification to pass 4818 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 4819 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 4820 * appear more prominently in the user interface. 4821 * </P> 4822 * 4823 * <P> 4824 * A person should usually contain a uri in order to benefit from the ranking boost. 4825 * However, even if no uri is provided, it's beneficial to provide other people in the 4826 * notification, such that listeners and voice only devices can announce and handle them 4827 * properly. 4828 * </P> 4829 * 4830 * @param person the person to add. 4831 * @see Notification#EXTRA_PEOPLE_LIST 4832 */ 4833 @NonNull addPerson(Person person)4834 public Builder addPerson(Person person) { 4835 mPersonList.add(person); 4836 return this; 4837 } 4838 4839 /** 4840 * Set this notification to be part of a group of notifications sharing the same key. 4841 * Grouped notifications may display in a cluster or stack on devices which 4842 * support such rendering. 4843 * 4844 * <p>To make this notification the summary for its group, also call 4845 * {@link #setGroupSummary}. A sort order can be specified for group members by using 4846 * {@link #setSortKey}. 4847 * @param groupKey The group key of the group. 4848 * @return this object for method chaining 4849 */ 4850 @NonNull setGroup(String groupKey)4851 public Builder setGroup(String groupKey) { 4852 mN.mGroupKey = groupKey; 4853 return this; 4854 } 4855 4856 /** 4857 * Set this notification to be the group summary for a group of notifications. 4858 * Grouped notifications may display in a cluster or stack on devices which 4859 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 4860 * The group summary may be suppressed if too few notifications are included in the group. 4861 * @param isGroupSummary Whether this notification should be a group summary. 4862 * @return this object for method chaining 4863 */ 4864 @NonNull setGroupSummary(boolean isGroupSummary)4865 public Builder setGroupSummary(boolean isGroupSummary) { 4866 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 4867 return this; 4868 } 4869 4870 /** 4871 * Set a sort key that orders this notification among other notifications from the 4872 * same package. This can be useful if an external sort was already applied and an app 4873 * would like to preserve this. Notifications will be sorted lexicographically using this 4874 * value, although providing different priorities in addition to providing sort key may 4875 * cause this value to be ignored. 4876 * 4877 * <p>This sort key can also be used to order members of a notification group. See 4878 * {@link #setGroup}. 4879 * 4880 * @see String#compareTo(String) 4881 */ 4882 @NonNull setSortKey(String sortKey)4883 public Builder setSortKey(String sortKey) { 4884 mN.mSortKey = sortKey; 4885 return this; 4886 } 4887 4888 /** 4889 * Merge additional metadata into this notification. 4890 * 4891 * <p>Values within the Bundle will replace existing extras values in this Builder. 4892 * 4893 * @see Notification#extras 4894 */ 4895 @NonNull addExtras(Bundle extras)4896 public Builder addExtras(Bundle extras) { 4897 if (extras != null) { 4898 mUserExtras.putAll(extras); 4899 } 4900 return this; 4901 } 4902 4903 /** 4904 * Set metadata for this notification. 4905 * 4906 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 4907 * current contents are copied into the Notification each time {@link #build()} is 4908 * called. 4909 * 4910 * <p>Replaces any existing extras values with those from the provided Bundle. 4911 * Use {@link #addExtras} to merge in metadata instead. 4912 * 4913 * @see Notification#extras 4914 */ 4915 @NonNull setExtras(Bundle extras)4916 public Builder setExtras(Bundle extras) { 4917 if (extras != null) { 4918 mUserExtras = extras; 4919 } 4920 return this; 4921 } 4922 4923 /** 4924 * Get the current metadata Bundle used by this notification Builder. 4925 * 4926 * <p>The returned Bundle is shared with this Builder. 4927 * 4928 * <p>The current contents of this Bundle are copied into the Notification each time 4929 * {@link #build()} is called. 4930 * 4931 * @see Notification#extras 4932 */ getExtras()4933 public Bundle getExtras() { 4934 return mUserExtras; 4935 } 4936 getAllExtras()4937 private Bundle getAllExtras() { 4938 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 4939 saveExtras.putAll(mN.extras); 4940 return saveExtras; 4941 } 4942 4943 /** 4944 * Add an action to this notification. Actions are typically displayed by 4945 * the system as a button adjacent to the notification content. 4946 * <p> 4947 * Every action must have an icon (32dp square and matching the 4948 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4949 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4950 * <p> 4951 * A notification in its expanded form can display up to 3 actions, from left to right in 4952 * the order they were added. Actions will not be displayed when the notification is 4953 * collapsed, however, so be sure that any essential functions may be accessed by the user 4954 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4955 * <p> 4956 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4957 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4958 * while processing broadcast receivers or services in response to notification action 4959 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the 4960 * activity itself. 4961 * <p> 4962 * As of Android {@link android.os.Build.VERSION_CODES#N}, 4963 * action button icons will not be displayed on action buttons, but are still required 4964 * and are available to 4965 * {@link android.service.notification.NotificationListenerService notification listeners}, 4966 * which may display them in other contexts, for example on a wearable device. 4967 * 4968 * @param icon Resource ID of a drawable that represents the action. 4969 * @param title Text describing the action. 4970 * @param intent PendingIntent to be fired when the action is invoked. 4971 * 4972 * @deprecated Use {@link #addAction(Action)} instead. 4973 */ 4974 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)4975 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 4976 mActions.add(new Action(icon, safeCharSequence(title), intent)); 4977 return this; 4978 } 4979 4980 /** 4981 * Add an action to this notification. Actions are typically displayed by 4982 * the system as a button adjacent to the notification content. 4983 * <p> 4984 * Every action must have an icon (32dp square and matching the 4985 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 4986 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 4987 * <p> 4988 * A notification in its expanded form can display up to 3 actions, from left to right in 4989 * the order they were added. Actions will not be displayed when the notification is 4990 * collapsed, however, so be sure that any essential functions may be accessed by the user 4991 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 4992 * 4993 * @param action The action to add. 4994 */ 4995 @NonNull addAction(Action action)4996 public Builder addAction(Action action) { 4997 if (action != null) { 4998 mActions.add(action); 4999 } 5000 return this; 5001 } 5002 5003 /** 5004 * Alter the complete list of actions attached to this notification. 5005 * @see #addAction(Action). 5006 * 5007 * @param actions 5008 * @return 5009 */ 5010 @NonNull setActions(Action... actions)5011 public Builder setActions(Action... actions) { 5012 mActions.clear(); 5013 for (int i = 0; i < actions.length; i++) { 5014 if (actions[i] != null) { 5015 mActions.add(actions[i]); 5016 } 5017 } 5018 return this; 5019 } 5020 5021 /** 5022 * Add a rich notification style to be applied at build time. 5023 * 5024 * @param style Object responsible for modifying the notification style. 5025 */ 5026 @NonNull setStyle(Style style)5027 public Builder setStyle(Style style) { 5028 if (mStyle != style) { 5029 mStyle = style; 5030 if (mStyle != null) { 5031 mStyle.setBuilder(this); 5032 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 5033 } else { 5034 mN.extras.remove(EXTRA_TEMPLATE); 5035 } 5036 } 5037 return this; 5038 } 5039 5040 /** 5041 * Returns the style set by {@link #setStyle(Style)}. 5042 */ getStyle()5043 public Style getStyle() { 5044 return mStyle; 5045 } 5046 5047 /** 5048 * Specify the value of {@link #visibility}. 5049 * 5050 * @return The same Builder. 5051 */ 5052 @NonNull setVisibility(@isibility int visibility)5053 public Builder setVisibility(@Visibility int visibility) { 5054 mN.visibility = visibility; 5055 return this; 5056 } 5057 5058 /** 5059 * Supply a replacement Notification whose contents should be shown in insecure contexts 5060 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 5061 * @param n A replacement notification, presumably with some or all info redacted. 5062 * @return The same Builder. 5063 */ 5064 @NonNull setPublicVersion(Notification n)5065 public Builder setPublicVersion(Notification n) { 5066 if (n != null) { 5067 mN.publicVersion = new Notification(); 5068 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 5069 } else { 5070 mN.publicVersion = null; 5071 } 5072 return this; 5073 } 5074 5075 /** 5076 * Apply an extender to this notification builder. Extenders may be used to add 5077 * metadata or change options on this builder. 5078 */ 5079 @NonNull extend(Extender extender)5080 public Builder extend(Extender extender) { 5081 extender.extend(this); 5082 return this; 5083 } 5084 5085 /** 5086 * Set the value for a notification flag 5087 * 5088 * @param mask Bit mask of the flag 5089 * @param value Status (on/off) of the flag 5090 * 5091 * @return The same Builder. 5092 */ 5093 @NonNull setFlag(@otificationFlags int mask, boolean value)5094 public Builder setFlag(@NotificationFlags int mask, boolean value) { 5095 if (value) { 5096 mN.flags |= mask; 5097 } else { 5098 mN.flags &= ~mask; 5099 } 5100 return this; 5101 } 5102 5103 /** 5104 * Sets {@link Notification#color}. 5105 * 5106 * @param argb The accent color to use 5107 * 5108 * @return The same Builder. 5109 */ 5110 @NonNull setColor(@olorInt int argb)5111 public Builder setColor(@ColorInt int argb) { 5112 mN.color = argb; 5113 sanitizeColor(); 5114 return this; 5115 } 5116 bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5117 private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { 5118 contentView.setDrawableTint( 5119 R.id.phishing_alert, 5120 false /* targetBackground */, 5121 getColors(p).getErrorColor(), 5122 PorterDuff.Mode.SRC_ATOP); 5123 } 5124 getProfileBadgeDrawable()5125 private Drawable getProfileBadgeDrawable() { 5126 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 5127 // This user can never be a badged profile, 5128 // and also includes USER_ALL system notifications. 5129 return null; 5130 } 5131 // Note: This assumes that the current user can read the profile badge of the 5132 // originating user. 5133 DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); 5134 return dpm.getResources().getDrawable( 5135 getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION, 5136 this::getDefaultProfileBadgeDrawable); 5137 } 5138 getUpdatableProfileBadgeId()5139 private String getUpdatableProfileBadgeId() { 5140 return mContext.getSystemService(UserManager.class).isManagedProfile() 5141 ? WORK_PROFILE_ICON : UNDEFINED; 5142 } 5143 getDefaultProfileBadgeDrawable()5144 private Drawable getDefaultProfileBadgeDrawable() { 5145 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 5146 new UserHandle(mContext.getUserId()), 0); 5147 } 5148 getProfileBadge()5149 private Bitmap getProfileBadge() { 5150 Drawable badge = getProfileBadgeDrawable(); 5151 if (badge == null) { 5152 return null; 5153 } 5154 final int size = mContext.getResources().getDimensionPixelSize( 5155 R.dimen.notification_badge_size); 5156 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 5157 Canvas canvas = new Canvas(bitmap); 5158 badge.setBounds(0, 0, size, size); 5159 badge.draw(canvas); 5160 return bitmap; 5161 } 5162 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5163 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 5164 Bitmap profileBadge = getProfileBadge(); 5165 5166 if (profileBadge != null) { 5167 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 5168 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 5169 if (isBackgroundColorized(p)) { 5170 contentView.setDrawableTint(R.id.profile_badge, false, 5171 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 5172 } 5173 } 5174 } 5175 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5176 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 5177 contentView.setDrawableTint( 5178 R.id.alerted_icon, 5179 false /* targetBackground */, 5180 getColors(p).getSecondaryTextColor(), 5181 PorterDuff.Mode.SRC_IN); 5182 } 5183 5184 /** 5185 * @hide 5186 */ usesStandardHeader()5187 public boolean usesStandardHeader() { 5188 if (mN.mUsesStandardHeader) { 5189 return true; 5190 } 5191 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 5192 if (mN.contentView == null && mN.bigContentView == null) { 5193 return true; 5194 } 5195 } 5196 boolean contentViewUsesHeader = mN.contentView == null 5197 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 5198 boolean bigContentViewUsesHeader = mN.bigContentView == null 5199 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 5200 return contentViewUsesHeader && bigContentViewUsesHeader; 5201 } 5202 resetStandardTemplate(RemoteViews contentView)5203 private void resetStandardTemplate(RemoteViews contentView) { 5204 resetNotificationHeader(contentView); 5205 contentView.setViewVisibility(R.id.right_icon, View.GONE); 5206 contentView.setViewVisibility(R.id.title, View.GONE); 5207 contentView.setTextViewText(R.id.title, null); 5208 contentView.setViewVisibility(R.id.text, View.GONE); 5209 contentView.setTextViewText(R.id.text, null); 5210 } 5211 5212 /** 5213 * Resets the notification header to its original state 5214 */ resetNotificationHeader(RemoteViews contentView)5215 private void resetNotificationHeader(RemoteViews contentView) { 5216 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 5217 // re-using the drawable when the notification is updated. 5218 contentView.setBoolean(R.id.expand_button, "setExpanded", false); 5219 contentView.setViewVisibility(R.id.app_name_text, View.GONE); 5220 contentView.setTextViewText(R.id.app_name_text, null); 5221 contentView.setViewVisibility(R.id.chronometer, View.GONE); 5222 contentView.setViewVisibility(R.id.header_text, View.GONE); 5223 contentView.setTextViewText(R.id.header_text, null); 5224 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 5225 contentView.setTextViewText(R.id.header_text_secondary, null); 5226 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 5227 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 5228 contentView.setViewVisibility(R.id.time_divider, View.GONE); 5229 contentView.setViewVisibility(R.id.time, View.GONE); 5230 contentView.setImageViewIcon(R.id.profile_badge, null); 5231 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 5232 mN.mUsesStandardHeader = false; 5233 } 5234 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5235 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 5236 TemplateBindResult result) { 5237 p.headerless(resId == getBaseLayoutResource() 5238 || resId == getHeadsUpBaseLayoutResource() 5239 || resId == getMessagingLayoutResource() 5240 || resId == R.layout.notification_template_material_media); 5241 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 5242 5243 resetStandardTemplate(contentView); 5244 5245 final Bundle ex = mN.extras; 5246 updateBackgroundColor(contentView, p); 5247 bindNotificationHeader(contentView, p); 5248 bindLargeIconAndApplyMargin(contentView, p, result); 5249 boolean showProgress = handleProgressBar(contentView, ex, p); 5250 boolean hasSecondLine = showProgress; 5251 if (p.hasTitle()) { 5252 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE); 5253 contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title)); 5254 setTextViewColorPrimary(contentView, p.mTitleViewId, p); 5255 } else if (p.mTitleViewId != R.id.title) { 5256 // This alternate title view ID is not cleared by resetStandardTemplate 5257 contentView.setViewVisibility(p.mTitleViewId, View.GONE); 5258 contentView.setTextViewText(p.mTitleViewId, null); 5259 } 5260 if (p.text != null && p.text.length() != 0 5261 && (!showProgress || p.mAllowTextWithProgress)) { 5262 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE); 5263 contentView.setTextViewText(p.mTextViewId, processTextSpans(p.text)); 5264 setTextViewColorSecondary(contentView, p.mTextViewId, p); 5265 hasSecondLine = true; 5266 } else if (p.mTextViewId != R.id.text) { 5267 // This alternate text view ID is not cleared by resetStandardTemplate 5268 contentView.setViewVisibility(p.mTextViewId, View.GONE); 5269 contentView.setTextViewText(p.mTextViewId, null); 5270 } 5271 setHeaderlessVerticalMargins(contentView, p, hasSecondLine); 5272 5273 return contentView; 5274 } 5275 setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5276 private static void setHeaderlessVerticalMargins(RemoteViews contentView, 5277 StandardTemplateParams p, boolean hasSecondLine) { 5278 if (!p.mHeaderless) { 5279 return; 5280 } 5281 int marginDimen = hasSecondLine 5282 ? R.dimen.notification_headerless_margin_twoline 5283 : R.dimen.notification_headerless_margin_oneline; 5284 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5285 RemoteViews.MARGIN_TOP, marginDimen); 5286 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5287 RemoteViews.MARGIN_BOTTOM, marginDimen); 5288 } 5289 processTextSpans(CharSequence text)5290 private CharSequence processTextSpans(CharSequence text) { 5291 if (mInNightMode) { 5292 return ContrastColorUtil.clearColorSpans(text); 5293 } 5294 return text; 5295 } 5296 setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5297 private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, 5298 StandardTemplateParams p) { 5299 contentView.setTextColor(id, getPrimaryTextColor(p)); 5300 } 5301 5302 /** 5303 * @param p the template params to inflate this with 5304 * @return the primary text color 5305 * @hide 5306 */ 5307 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)5308 public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { 5309 return getColors(p).getPrimaryTextColor(); 5310 } 5311 5312 /** 5313 * @param p the template params to inflate this with 5314 * @return the secondary text color 5315 * @hide 5316 */ 5317 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)5318 public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { 5319 return getColors(p).getSecondaryTextColor(); 5320 } 5321 setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5322 private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, 5323 StandardTemplateParams p) { 5324 contentView.setTextColor(id, getSecondaryTextColor(p)); 5325 } 5326 getColors(StandardTemplateParams p)5327 private Colors getColors(StandardTemplateParams p) { 5328 mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode); 5329 return mColors; 5330 } 5331 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5332 private void updateBackgroundColor(RemoteViews contentView, 5333 StandardTemplateParams p) { 5334 if (isBackgroundColorized(p)) { 5335 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 5336 getBackgroundColor(p)); 5337 } else { 5338 // Clear it! 5339 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 5340 0); 5341 } 5342 } 5343 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5344 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 5345 StandardTemplateParams p) { 5346 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 5347 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 5348 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5349 if (!p.mHideProgress && (max != 0 || ind)) { 5350 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 5351 contentView.setProgressBar(R.id.progress, max, progress, ind); 5352 contentView.setProgressBackgroundTintList(R.id.progress, 5353 mContext.getColorStateList(R.color.notification_progress_background_color)); 5354 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p)); 5355 contentView.setProgressTintList(R.id.progress, progressTint); 5356 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint); 5357 return true; 5358 } else { 5359 contentView.setViewVisibility(R.id.progress, View.GONE); 5360 return false; 5361 } 5362 } 5363 bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5364 private void bindLargeIconAndApplyMargin(RemoteViews contentView, 5365 @NonNull StandardTemplateParams p, 5366 @Nullable TemplateBindResult result) { 5367 if (result == null) { 5368 result = new TemplateBindResult(); 5369 } 5370 bindLargeIcon(contentView, p, result); 5371 if (!p.mHeaderless) { 5372 // views in states with a header (big states) 5373 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header); 5374 result.mTitleMarginSet.applyToView(contentView, R.id.title); 5375 // If there is no title, the text (or big_text) needs to wrap around the image 5376 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId); 5377 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1); 5378 } 5379 } 5380 5381 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5382 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5383 // the change's state in NotificationManagerService were very complex. These behavior 5384 // changes are entirely visual, and should otherwise be undetectable by apps. 5385 @SuppressWarnings("AndroidFrameworkCompatChange") calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5386 private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, 5387 @NonNull TemplateBindResult result) { 5388 final Resources resources = mContext.getResources(); 5389 final float density = resources.getDisplayMetrics().density; 5390 final float iconMarginDp = resources.getDimension( 5391 R.dimen.notification_right_icon_content_margin) / density; 5392 final float contentMarginDp = resources.getDimension( 5393 R.dimen.notification_content_margin_end) / density; 5394 final float expanderSizeDp = resources.getDimension( 5395 R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; 5396 final float viewHeightDp = resources.getDimension( 5397 R.dimen.notification_right_icon_size) / density; 5398 float viewWidthDp = viewHeightDp; // icons are 1:1 by default 5399 if (rightIcon != null && (isPromotedPicture 5400 || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) { 5401 Drawable drawable = rightIcon.loadDrawable(mContext); 5402 if (drawable != null) { 5403 int iconWidth = drawable.getIntrinsicWidth(); 5404 int iconHeight = drawable.getIntrinsicHeight(); 5405 if (iconWidth > iconHeight && iconHeight > 0) { 5406 final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO; 5407 viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight, 5408 maxViewWidthDp); 5409 } 5410 } 5411 } 5412 final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; 5413 result.setRightIconState(rightIcon != null /* visible */, viewWidthDp, 5414 viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp); 5415 } 5416 5417 /** 5418 * Bind the large icon. 5419 */ bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5420 private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, 5421 @NonNull TemplateBindResult result) { 5422 if (mN.mLargeIcon == null && mN.largeIcon != null) { 5423 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 5424 } 5425 5426 // Determine the left and right icons 5427 Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon; 5428 Icon rightIcon = p.mHideRightIcon ? null 5429 : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon); 5430 5431 // Apply the left icon (without duplicating the bitmap) 5432 if (leftIcon != rightIcon || leftIcon == null) { 5433 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it 5434 // explicitly and make sure it won't take the right_icon drawable. 5435 contentView.setImageViewIcon(R.id.left_icon, leftIcon); 5436 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0); 5437 } else { 5438 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon 5439 // drawable. This avoids the view having two copies of the same bitmap. 5440 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1); 5441 } 5442 5443 // Always calculate dimens to populate `result` for the GONE case 5444 boolean isPromotedPicture = p.mPromotedPicture != null; 5445 calculateRightIconDimens(rightIcon, isPromotedPicture, result); 5446 5447 // Bind the right icon 5448 if (rightIcon != null) { 5449 contentView.setViewLayoutWidth(R.id.right_icon, 5450 result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP); 5451 contentView.setViewLayoutHeight(R.id.right_icon, 5452 result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP); 5453 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 5454 contentView.setImageViewIcon(R.id.right_icon, rightIcon); 5455 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 5456 isPromotedPicture ? 1 : 0); 5457 processLargeLegacyIcon(rightIcon, contentView, p); 5458 } else { 5459 // The "reset" doesn't clear the drawable, so we do it here. This clear is 5460 // important because the presence of a drawable in this view (regardless of the 5461 // visibility) is used by NotificationGroupingUtil to set the visibility. 5462 contentView.setImageViewIcon(R.id.right_icon, null); 5463 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0); 5464 } 5465 } 5466 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)5467 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 5468 bindSmallIcon(contentView, p); 5469 // Populate text left-to-right so that separators are only shown between strings 5470 boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */); 5471 hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft); 5472 hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft); 5473 if (!hasTextToLeft) { 5474 // If there's still no text, force add the app name so there is some text. 5475 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */); 5476 } 5477 bindHeaderChronometerAndTime(contentView, p, hasTextToLeft); 5478 bindPhishingAlertIcon(contentView, p); 5479 bindProfileBadge(contentView, p); 5480 bindAlertedIcon(contentView, p); 5481 bindExpandButton(contentView, p); 5482 mN.mUsesStandardHeader = true; 5483 } 5484 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)5485 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 5486 // set default colors 5487 int bgColor = getBackgroundColor(p); 5488 int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor); 5489 int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor); 5490 contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor); 5491 contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); 5492 // Use different highlighted colors for conversations' unread count 5493 if (p.mHighlightExpander) { 5494 pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor); 5495 textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor); 5496 } 5497 contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); 5498 contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); 5499 } 5500 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5501 private void bindHeaderChronometerAndTime(RemoteViews contentView, 5502 StandardTemplateParams p, boolean hasTextToLeft) { 5503 if (!p.mHideTime && showsTimeOrChronometer()) { 5504 if (hasTextToLeft) { 5505 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 5506 setTextViewColorSecondary(contentView, R.id.time_divider, p); 5507 } 5508 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 5509 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 5510 contentView.setLong(R.id.chronometer, "setBase", 5511 mN.when + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 5512 contentView.setBoolean(R.id.chronometer, "setStarted", true); 5513 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 5514 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 5515 setTextViewColorSecondary(contentView, R.id.chronometer, p); 5516 } else { 5517 contentView.setViewVisibility(R.id.time, View.VISIBLE); 5518 contentView.setLong(R.id.time, "setTime", mN.when); 5519 setTextViewColorSecondary(contentView, R.id.time, p); 5520 } 5521 } else { 5522 // We still want a time to be set but gone, such that we can show and hide it 5523 // on demand in case it's a child notification without anything in the header 5524 contentView.setLong(R.id.time, "setTime", mN.when != 0 ? mN.when : mN.creationTime); 5525 setTextViewColorSecondary(contentView, R.id.time, p); 5526 } 5527 } 5528 5529 /** 5530 * @return true if the header text will be visible 5531 */ bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5532 private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p, 5533 boolean hasTextToLeft) { 5534 if (p.mHideSubText) { 5535 return false; 5536 } 5537 CharSequence summaryText = p.summaryText; 5538 if (summaryText == null && mStyle != null && mStyle.mSummaryTextSet 5539 && mStyle.hasSummaryInHeader()) { 5540 summaryText = mStyle.mSummaryText; 5541 } 5542 if (summaryText == null 5543 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 5544 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 5545 summaryText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 5546 } 5547 if (!TextUtils.isEmpty(summaryText)) { 5548 // TODO: Remove the span entirely to only have the string with propper formating. 5549 contentView.setTextViewText(R.id.header_text, processTextSpans( 5550 processLegacyText(summaryText))); 5551 setTextViewColorSecondary(contentView, R.id.header_text, p); 5552 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 5553 if (hasTextToLeft) { 5554 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 5555 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 5556 } 5557 return true; 5558 } 5559 return false; 5560 } 5561 5562 /** 5563 * @return true if the secondary header text will be visible 5564 */ bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)5565 private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, 5566 boolean hasTextToLeft) { 5567 if (p.mHideSubText) { 5568 return false; 5569 } 5570 if (!TextUtils.isEmpty(p.headerTextSecondary)) { 5571 contentView.setTextViewText(R.id.header_text_secondary, processTextSpans( 5572 processLegacyText(p.headerTextSecondary))); 5573 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 5574 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 5575 if (hasTextToLeft) { 5576 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 5577 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 5578 } 5579 return true; 5580 } 5581 return false; 5582 } 5583 5584 /** 5585 * @hide 5586 */ 5587 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) loadHeaderAppName()5588 public String loadHeaderAppName() { 5589 CharSequence name = null; 5590 final PackageManager pm = mContext.getPackageManager(); 5591 if (mN.extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 5592 // only system packages which lump together a bunch of unrelated stuff 5593 // may substitute a different name to make the purpose of the 5594 // notification more clear. the correct package label should always 5595 // be accessible via SystemUI. 5596 final String pkg = mContext.getPackageName(); 5597 final String subName = mN.extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 5598 if (PackageManager.PERMISSION_GRANTED == pm.checkPermission( 5599 android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME, pkg)) { 5600 name = subName; 5601 } else { 5602 Log.w(TAG, "warning: pkg " 5603 + pkg + " attempting to substitute app name '" + subName 5604 + "' without holding perm " 5605 + android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME); 5606 } 5607 } 5608 if (TextUtils.isEmpty(name)) { 5609 name = pm.getApplicationLabel(mContext.getApplicationInfo()); 5610 } 5611 if (TextUtils.isEmpty(name)) { 5612 // still nothing? 5613 return null; 5614 } 5615 5616 return String.valueOf(name); 5617 } 5618 5619 /** 5620 * @return true if the app name will be visible 5621 */ bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)5622 private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, 5623 boolean force) { 5624 if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) { 5625 // unless the force flag is set, don't show the app name in the minimized state. 5626 return false; 5627 } 5628 if (p.mHeaderless && p.hasTitle()) { 5629 // the headerless template will have the TITLE in this position; return true to 5630 // keep the divider visible between that title and the next text element. 5631 return true; 5632 } 5633 if (p.mHideAppName) { 5634 // The app name is being hidden, so we definitely want to return here. 5635 // Assume that there is a title which will replace it in the header. 5636 return p.hasTitle(); 5637 } 5638 contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); 5639 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 5640 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 5641 return true; 5642 } 5643 5644 /** 5645 * Determines if the notification should be colorized *for the purposes of applying colors*. 5646 * If this is the minimized view of a colorized notification, this will return false so that 5647 * internal coloring logic can still render the notification normally. 5648 */ isBackgroundColorized(StandardTemplateParams p)5649 private boolean isBackgroundColorized(StandardTemplateParams p) { 5650 return p.allowColorization && mN.isColorized(); 5651 } 5652 isCallActionColorCustomizable()5653 private boolean isCallActionColorCustomizable() { 5654 // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because 5655 // that is only used for disallowing colorization of headers for the minimized state, 5656 // and neither of those conditions applies when showing actions. 5657 // Not requiring StandardTemplateParams as an argument simplifies the creation process. 5658 return mN.isColorized() && mContext.getResources().getBoolean( 5659 R.bool.config_callNotificationActionColorsRequireColorized); 5660 } 5661 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)5662 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 5663 if (mN.mSmallIcon == null && mN.icon != 0) { 5664 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 5665 } 5666 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 5667 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 5668 processSmallIconColor(mN.mSmallIcon, contentView, p); 5669 } 5670 5671 /** 5672 * @return true if the built notification will show the time or the chronometer; false 5673 * otherwise 5674 */ showsTimeOrChronometer()5675 private boolean showsTimeOrChronometer() { 5676 return mN.showsTime() || mN.showsChronometer(); 5677 } 5678 resetStandardTemplateWithActions(RemoteViews big)5679 private void resetStandardTemplateWithActions(RemoteViews big) { 5680 // actions_container is only reset when there are no actions to avoid focus issues with 5681 // remote inputs. 5682 big.setViewVisibility(R.id.actions, View.GONE); 5683 big.removeAllViews(R.id.actions); 5684 5685 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 5686 big.setTextViewText(R.id.notification_material_reply_text_1, null); 5687 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 5688 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 5689 5690 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 5691 big.setTextViewText(R.id.notification_material_reply_text_2, null); 5692 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 5693 big.setTextViewText(R.id.notification_material_reply_text_3, null); 5694 5695 // This may get erased by bindSnoozeAction 5696 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5697 RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin); 5698 } 5699 bindSnoozeAction(RemoteViews big, StandardTemplateParams p)5700 private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) { 5701 boolean hideSnoozeButton = mN.isForegroundService() || mN.fullScreenIntent != null 5702 || isBackgroundColorized(p) 5703 || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG; 5704 big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton); 5705 if (hideSnoozeButton) { 5706 // Only hide; NotificationContentView will show it when it adds the click listener 5707 big.setViewVisibility(R.id.snooze_button, View.GONE); 5708 } 5709 5710 final boolean snoozeEnabled = !hideSnoozeButton 5711 && mContext.getContentResolver() != null 5712 && isSnoozeSettingEnabled(); 5713 if (snoozeEnabled) { 5714 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5715 RemoteViews.MARGIN_BOTTOM, 0); 5716 } 5717 } 5718 isSnoozeSettingEnabled()5719 private boolean isSnoozeSettingEnabled() { 5720 try { 5721 return Settings.Secure.getInt(mContext.getContentResolver(), 5722 Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0) == 1; 5723 } catch (SecurityException ex) { 5724 // Most 3p apps can't access this snooze setting, so their NotificationListeners 5725 // would be unable to create notification views if we propagated this exception. 5726 return false; 5727 } 5728 } 5729 5730 /** 5731 * Returns the actions that are not contextual. 5732 */ getNonContextualActions()5733 private @NonNull List<Notification.Action> getNonContextualActions() { 5734 if (mActions == null) return Collections.emptyList(); 5735 List<Notification.Action> standardActions = new ArrayList<>(); 5736 for (Notification.Action action : mActions) { 5737 if (!action.isContextual()) { 5738 standardActions.add(action); 5739 } 5740 } 5741 return standardActions; 5742 } 5743 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)5744 private RemoteViews applyStandardTemplateWithActions(int layoutId, 5745 StandardTemplateParams p, TemplateBindResult result) { 5746 RemoteViews big = applyStandardTemplate(layoutId, p, result); 5747 5748 resetStandardTemplateWithActions(big); 5749 bindSnoozeAction(big, p); 5750 // color the snooze and bubble actions with the theme color 5751 ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); 5752 big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); 5753 big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); 5754 5755 boolean validRemoteInput = false; 5756 5757 // In the UI, contextual actions appear separately from the standard actions, so we 5758 // filter them out here. 5759 List<Notification.Action> nonContextualActions = getNonContextualActions(); 5760 5761 int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS); 5762 boolean emphazisedMode = mN.fullScreenIntent != null || p.mCallStyleActions; 5763 if (p.mCallStyleActions) { 5764 // Clear view padding to allow buttons to start on the left edge. 5765 // This must be done before 'setEmphasizedMode' which sets top/bottom margins. 5766 big.setViewPadding(R.id.actions, 0, 0, 0, 0); 5767 // Add an optional indent that will make buttons start at the correct column when 5768 // there is enough space to do so (and fall back to the left edge if not). 5769 big.setInt(R.id.actions, "setCollapsibleIndentDimen", 5770 R.dimen.call_notification_collapsible_indent); 5771 } 5772 big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode); 5773 if (numActions > 0 && !p.mHideActions) { 5774 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 5775 big.setViewVisibility(R.id.actions, View.VISIBLE); 5776 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 5777 RemoteViews.MARGIN_BOTTOM, 0); 5778 for (int i = 0; i < numActions; i++) { 5779 Action action = nonContextualActions.get(i); 5780 5781 boolean actionHasValidInput = hasValidRemoteInput(action); 5782 validRemoteInput |= actionHasValidInput; 5783 5784 final RemoteViews button = generateActionButton(action, emphazisedMode, p); 5785 if (actionHasValidInput && !emphazisedMode) { 5786 // Clear the drawable 5787 button.setInt(R.id.action0, "setBackgroundResource", 0); 5788 } 5789 if (emphazisedMode && i > 0) { 5790 // Clear start margin from non-first buttons to reduce the gap between them. 5791 // (8dp remaining gap is from all buttons' standard 4dp inset). 5792 button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0); 5793 } 5794 big.addView(R.id.actions, button); 5795 } 5796 } else { 5797 big.setViewVisibility(R.id.actions_container, View.GONE); 5798 } 5799 5800 RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( 5801 mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); 5802 if (validRemoteInput && replyText != null && replyText.length > 0 5803 && !TextUtils.isEmpty(replyText[0].getText()) 5804 && p.maxRemoteInputHistory > 0) { 5805 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 5806 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 5807 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 5808 View.VISIBLE); 5809 big.setTextViewText(R.id.notification_material_reply_text_1, 5810 processTextSpans(replyText[0].getText())); 5811 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 5812 big.setViewVisibility(R.id.notification_material_reply_progress, 5813 showSpinner ? View.VISIBLE : View.GONE); 5814 big.setProgressIndeterminateTintList( 5815 R.id.notification_material_reply_progress, 5816 ColorStateList.valueOf(getPrimaryAccentColor(p))); 5817 5818 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) 5819 && p.maxRemoteInputHistory > 1) { 5820 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 5821 big.setTextViewText(R.id.notification_material_reply_text_2, 5822 processTextSpans(replyText[1].getText())); 5823 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 5824 5825 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) 5826 && p.maxRemoteInputHistory > 2) { 5827 big.setViewVisibility( 5828 R.id.notification_material_reply_text_3, View.VISIBLE); 5829 big.setTextViewText(R.id.notification_material_reply_text_3, 5830 processTextSpans(replyText[2].getText())); 5831 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 5832 } 5833 } 5834 } 5835 5836 return big; 5837 } 5838 hasValidRemoteInput(Action action)5839 private boolean hasValidRemoteInput(Action action) { 5840 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 5841 // Weird actions 5842 return false; 5843 } 5844 5845 RemoteInput[] remoteInputs = action.getRemoteInputs(); 5846 if (remoteInputs == null) { 5847 return false; 5848 } 5849 5850 for (RemoteInput r : remoteInputs) { 5851 CharSequence[] choices = r.getChoices(); 5852 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 5853 return true; 5854 } 5855 } 5856 return false; 5857 } 5858 5859 /** 5860 * Construct a RemoteViews for the final 1U notification layout. In order: 5861 * 1. Custom contentView from the caller 5862 * 2. Style's proposed content view 5863 * 3. Standard template view 5864 */ createContentView()5865 public RemoteViews createContentView() { 5866 return createContentView(false /* increasedheight */ ); 5867 } 5868 5869 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5870 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5871 // the change's state in NotificationManagerService were very complex. While it's possible 5872 // apps can detect the change, it's most likely that the changes will simply result in 5873 // visual regressions. 5874 @SuppressWarnings("AndroidFrameworkCompatChange") fullyCustomViewRequiresDecoration(boolean fromStyle)5875 private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) { 5876 // Custom views which come from a platform style class are safe, and thus do not need to 5877 // be wrapped. Any subclass of those styles has the opportunity to make arbitrary 5878 // changes to the RemoteViews, and thus can't be trusted as a fully vetted view. 5879 if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { 5880 return false; 5881 } 5882 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S; 5883 } 5884 minimallyDecoratedContentView(@onNull RemoteViews customContent)5885 private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) { 5886 StandardTemplateParams p = mParams.reset() 5887 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 5888 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5889 .fillTextsFrom(this); 5890 TemplateBindResult result = new TemplateBindResult(); 5891 RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result); 5892 buildCustomContentIntoTemplate(mContext, standard, customContent, 5893 p, result); 5894 return standard; 5895 } 5896 minimallyDecoratedBigContentView(@onNull RemoteViews customContent)5897 private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) { 5898 StandardTemplateParams p = mParams.reset() 5899 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 5900 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5901 .fillTextsFrom(this); 5902 TemplateBindResult result = new TemplateBindResult(); 5903 RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 5904 p, result); 5905 buildCustomContentIntoTemplate(mContext, standard, customContent, 5906 p, result); 5907 makeHeaderExpanded(standard); 5908 return standard; 5909 } 5910 minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)5911 private RemoteViews minimallyDecoratedHeadsUpContentView( 5912 @NonNull RemoteViews customContent) { 5913 StandardTemplateParams p = mParams.reset() 5914 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 5915 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 5916 .fillTextsFrom(this); 5917 TemplateBindResult result = new TemplateBindResult(); 5918 RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), 5919 p, result); 5920 buildCustomContentIntoTemplate(mContext, standard, customContent, 5921 p, result); 5922 return standard; 5923 } 5924 5925 /** 5926 * Construct a RemoteViews for the smaller content view. 5927 * 5928 * @param increasedHeight true if this layout be created with an increased height. Some 5929 * styles may support showing more then just that basic 1U size 5930 * and the system may decide to render important notifications 5931 * slightly bigger even when collapsed. 5932 * 5933 * @hide 5934 */ createContentView(boolean increasedHeight)5935 public RemoteViews createContentView(boolean increasedHeight) { 5936 if (useExistingRemoteView(mN.contentView)) { 5937 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5938 ? minimallyDecoratedContentView(mN.contentView) : mN.contentView; 5939 } else if (mStyle != null) { 5940 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 5941 if (styleView != null) { 5942 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 5943 ? minimallyDecoratedContentView(styleView) : styleView; 5944 } 5945 } 5946 StandardTemplateParams p = mParams.reset() 5947 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 5948 .fillTextsFrom(this); 5949 return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */); 5950 } 5951 useExistingRemoteView(RemoteViews customContent)5952 private boolean useExistingRemoteView(RemoteViews customContent) { 5953 if (customContent == null) { 5954 return false; 5955 } 5956 if (styleDisplaysCustomViewInline()) { 5957 // the provided custom view is intended to be wrapped by the style. 5958 return false; 5959 } 5960 if (fullyCustomViewRequiresDecoration(false) 5961 && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) { 5962 // If the app's custom views are objects returned from Builder.create*ContentView() 5963 // then the app is most likely attempting to spoof the user. Even if they are not, 5964 // the result would be broken (b/189189308) so we will ignore it. 5965 Log.w(TAG, "For apps targeting S, a custom content view that is a modified " 5966 + "version of any standard layout is disallowed."); 5967 return false; 5968 } 5969 return true; 5970 } 5971 5972 /** 5973 * Construct a RemoteViews for the final big notification layout. 5974 */ createBigContentView()5975 public RemoteViews createBigContentView() { 5976 RemoteViews result = null; 5977 if (useExistingRemoteView(mN.bigContentView)) { 5978 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 5979 ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView; 5980 } 5981 if (mStyle != null) { 5982 result = mStyle.makeBigContentView(); 5983 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) { 5984 result = minimallyDecoratedBigContentView(result); 5985 } 5986 } 5987 if (result == null) { 5988 if (bigContentViewRequired()) { 5989 StandardTemplateParams p = mParams.reset() 5990 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 5991 .allowTextWithProgress(true) 5992 .fillTextsFrom(this); 5993 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, 5994 null /* result */); 5995 } 5996 } 5997 makeHeaderExpanded(result); 5998 return result; 5999 } 6000 6001 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 6002 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 6003 // the change's state in NotificationManagerService were very complex. While it's possible 6004 // apps can detect the change, it's most likely that the changes will simply result in 6005 // visual regressions. 6006 @SuppressWarnings("AndroidFrameworkCompatChange") bigContentViewRequired()6007 private boolean bigContentViewRequired() { 6008 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { 6009 return true; 6010 } 6011 // Notifications with contentView and without a bigContentView, style, or actions would 6012 // not have an expanded state before S, so showing the standard template expanded state 6013 // usually looks wrong, so we keep it simple and don't show the expanded state. 6014 boolean exempt = mN.contentView != null && mN.bigContentView == null 6015 && mStyle == null && mActions.size() == 0; 6016 return !exempt; 6017 } 6018 6019 /** 6020 * Construct a RemoteViews for the final notification header only. This will not be 6021 * colorized. 6022 * 6023 * @hide 6024 */ makeNotificationGroupHeader()6025 public RemoteViews makeNotificationGroupHeader() { 6026 return makeNotificationHeader(mParams.reset() 6027 .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER) 6028 .fillTextsFrom(this)); 6029 } 6030 6031 /** 6032 * Construct a RemoteViews for the final notification header only. This will not be 6033 * colorized. 6034 * 6035 * @param p the template params to inflate this with 6036 */ makeNotificationHeader(StandardTemplateParams p)6037 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 6038 // Headers on their own are never colorized 6039 p.disallowColorization(); 6040 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 6041 R.layout.notification_template_header); 6042 resetNotificationHeader(header); 6043 bindNotificationHeader(header, p); 6044 return header; 6045 } 6046 6047 /** 6048 * Construct a RemoteViews for the ambient version of the notification. 6049 * 6050 * @hide 6051 */ makeAmbientNotification()6052 public RemoteViews makeAmbientNotification() { 6053 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 6054 if (headsUpContentView != null) { 6055 return headsUpContentView; 6056 } 6057 return createContentView(); 6058 } 6059 6060 /** 6061 * Adapt the Notification header if this view is used as an expanded view. 6062 * 6063 * @hide 6064 */ makeHeaderExpanded(RemoteViews result)6065 public static void makeHeaderExpanded(RemoteViews result) { 6066 if (result != null) { 6067 result.setBoolean(R.id.expand_button, "setExpanded", true); 6068 } 6069 } 6070 6071 /** 6072 * Construct a RemoteViews for the final heads-up notification layout. 6073 * 6074 * @param increasedHeight true if this layout be created with an increased height. Some 6075 * styles may support showing more then just that basic 1U size 6076 * and the system may decide to render important notifications 6077 * slightly bigger even when collapsed. 6078 * 6079 * @hide 6080 */ createHeadsUpContentView(boolean increasedHeight)6081 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 6082 if (useExistingRemoteView(mN.headsUpContentView)) { 6083 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 6084 ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) 6085 : mN.headsUpContentView; 6086 } else if (mStyle != null) { 6087 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 6088 if (styleView != null) { 6089 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 6090 ? minimallyDecoratedHeadsUpContentView(styleView) : styleView; 6091 } 6092 } else if (mActions.size() == 0) { 6093 return null; 6094 } 6095 6096 // We only want at most a single remote input history to be shown here, otherwise 6097 // the content would become squished. 6098 StandardTemplateParams p = mParams.reset() 6099 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 6100 .fillTextsFrom(this) 6101 .setMaxRemoteInputHistory(1); 6102 return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, 6103 null /* result */); 6104 } 6105 6106 /** 6107 * Construct a RemoteViews for the final heads-up notification layout. 6108 */ createHeadsUpContentView()6109 public RemoteViews createHeadsUpContentView() { 6110 return createHeadsUpContentView(false /* useIncreasedHeight */); 6111 } 6112 6113 /** 6114 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 6115 * 6116 * @param isLowPriority is this notification low priority 6117 * @hide 6118 */ 6119 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)6120 public RemoteViews makePublicContentView(boolean isLowPriority) { 6121 if (mN.publicVersion != null) { 6122 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 6123 return builder.createContentView(); 6124 } 6125 Bundle savedBundle = mN.extras; 6126 Style style = mStyle; 6127 mStyle = null; 6128 Icon largeIcon = mN.mLargeIcon; 6129 mN.mLargeIcon = null; 6130 Bitmap largeIconLegacy = mN.largeIcon; 6131 mN.largeIcon = null; 6132 ArrayList<Action> actions = mActions; 6133 mActions = new ArrayList<>(); 6134 Bundle publicExtras = new Bundle(); 6135 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 6136 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 6137 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 6138 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 6139 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 6140 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 6141 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 6142 if (appName != null) { 6143 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 6144 } 6145 mN.extras = publicExtras; 6146 RemoteViews view; 6147 StandardTemplateParams params = mParams.reset() 6148 .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC) 6149 .fillTextsFrom(this); 6150 if (isLowPriority) { 6151 params.highlightExpander(false); 6152 } 6153 view = makeNotificationHeader(params); 6154 view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true); 6155 mN.extras = savedBundle; 6156 mN.mLargeIcon = largeIcon; 6157 mN.largeIcon = largeIconLegacy; 6158 mActions = actions; 6159 mStyle = style; 6160 return view; 6161 } 6162 6163 /** 6164 * Construct a content view for the display when low - priority 6165 * 6166 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise 6167 * a new subtext is created consisting of the content of the 6168 * notification. 6169 * @hide 6170 */ makeLowPriorityContentView(boolean useRegularSubtext)6171 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { 6172 StandardTemplateParams p = mParams.reset() 6173 .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED) 6174 .highlightExpander(false) 6175 .fillTextsFrom(this); 6176 if (!useRegularSubtext || TextUtils.isEmpty(p.summaryText)) { 6177 p.summaryText(createSummaryText()); 6178 } 6179 RemoteViews header = makeNotificationHeader(p); 6180 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true); 6181 // The low priority header has no app name and shows the text 6182 header.setBoolean(R.id.notification_header, "styleTextAsTitle", true); 6183 return header; 6184 } 6185 createSummaryText()6186 private CharSequence createSummaryText() { 6187 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE); 6188 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) { 6189 return titleText; 6190 } 6191 SpannableStringBuilder summary = new SpannableStringBuilder(); 6192 if (titleText == null) { 6193 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG); 6194 } 6195 BidiFormatter bidi = BidiFormatter.getInstance(); 6196 if (titleText != null) { 6197 summary.append(bidi.unicodeWrap(titleText)); 6198 } 6199 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT); 6200 if (titleText != null && contentText != null) { 6201 summary.append(bidi.unicodeWrap(mContext.getText( 6202 R.string.notification_header_divider_symbol_with_spaces))); 6203 } 6204 if (contentText != null) { 6205 summary.append(bidi.unicodeWrap(contentText)); 6206 } 6207 return summary; 6208 } 6209 generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6210 private RemoteViews generateActionButton(Action action, boolean emphasizedMode, 6211 StandardTemplateParams p) { 6212 final boolean tombstone = (action.actionIntent == null); 6213 final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 6214 getActionButtonLayoutResource(emphasizedMode, tombstone)); 6215 if (!tombstone) { 6216 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 6217 } 6218 button.setContentDescription(R.id.action0, action.title); 6219 if (action.mRemoteInputs != null) { 6220 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 6221 } 6222 if (emphasizedMode) { 6223 // change the background bgColor 6224 CharSequence title = action.title; 6225 int buttonFillColor = getColors(p).getSecondaryAccentColor(); 6226 if (tombstone) { 6227 buttonFillColor = setAlphaComponentByFloatDimen(mContext, 6228 ContrastColorUtil.resolveSecondaryColor( 6229 mContext, getColors(p).getBackgroundColor(), mInNightMode), 6230 R.dimen.notification_action_disabled_container_alpha); 6231 } 6232 if (isLegacy()) { 6233 title = ContrastColorUtil.clearColorSpans(title); 6234 } else { 6235 // Check for a full-length span color to use as the button fill color. 6236 Integer fullLengthColor = getFullLengthSpanColor(title); 6237 if (fullLengthColor != null) { 6238 // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. 6239 int notifBackgroundColor = getColors(p).getBackgroundColor(); 6240 buttonFillColor = ensureButtonFillContrast( 6241 fullLengthColor, notifBackgroundColor); 6242 } 6243 // Remove full-length color spans and ensure text contrast with the button fill. 6244 title = ensureColorSpanContrast(title, buttonFillColor); 6245 } 6246 button.setTextViewText(R.id.action0, processTextSpans(title)); 6247 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, 6248 buttonFillColor, mInNightMode); 6249 if (tombstone) { 6250 textColor = setAlphaComponentByFloatDimen(mContext, 6251 ContrastColorUtil.resolveSecondaryColor( 6252 mContext, getColors(p).getBackgroundColor(), mInNightMode), 6253 R.dimen.notification_action_disabled_content_alpha); 6254 } 6255 button.setTextColor(R.id.action0, textColor); 6256 // We only want about 20% alpha for the ripple 6257 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000; 6258 button.setColorStateList(R.id.action0, "setRippleColor", 6259 ColorStateList.valueOf(rippleColor)); 6260 button.setColorStateList(R.id.action0, "setButtonBackground", 6261 ColorStateList.valueOf(buttonFillColor)); 6262 if (p.mCallStyleActions) { 6263 button.setImageViewIcon(R.id.action0, action.getIcon()); 6264 boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY); 6265 button.setBoolean(R.id.action0, "setIsPriority", priority); 6266 int minWidthDimen = 6267 priority ? R.dimen.call_notification_system_action_min_width : 0; 6268 button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen); 6269 } 6270 } else { 6271 button.setTextViewText(R.id.action0, processTextSpans( 6272 processLegacyText(action.title))); 6273 button.setTextColor(R.id.action0, getStandardActionColor(p)); 6274 } 6275 // CallStyle notifications add action buttons which don't actually exist in mActions, 6276 // so we have to omit the index in that case. 6277 int actionIndex = mActions.indexOf(action); 6278 if (actionIndex != -1) { 6279 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex); 6280 } 6281 return button; 6282 } 6283 getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone)6284 private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) { 6285 if (emphasizedMode) { 6286 return tombstone ? getEmphasizedTombstoneActionLayoutResource() 6287 : getEmphasizedActionLayoutResource(); 6288 } else { 6289 return tombstone ? getActionTombstoneLayoutResource() 6290 : getActionLayoutResource(); 6291 } 6292 } 6293 6294 /** 6295 * Set the alpha component of {@code color} to be {@code alphaDimenResId}. 6296 */ setAlphaComponentByFloatDimen(Context context, @ColorInt int color, @DimenRes int alphaDimenResId)6297 private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color, 6298 @DimenRes int alphaDimenResId) { 6299 final TypedValue alphaValue = new TypedValue(); 6300 context.getResources().getValue(alphaDimenResId, alphaValue, true); 6301 return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255)); 6302 } 6303 6304 /** 6305 * Extract the color from a full-length span from the text. 6306 * 6307 * @param charSequence the charSequence containing spans 6308 * @return the raw color of the text's last full-length span containing a color, or null if 6309 * no full-length span sets the text color. 6310 * @hide 6311 */ 6312 @VisibleForTesting 6313 @Nullable getFullLengthSpanColor(CharSequence charSequence)6314 public static Integer getFullLengthSpanColor(CharSequence charSequence) { 6315 // NOTE: this method preserves the functionality that for a CharSequence with multiple 6316 // full-length spans, the color of the last one is used. 6317 Integer result = null; 6318 if (charSequence instanceof Spanned) { 6319 Spanned ss = (Spanned) charSequence; 6320 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 6321 // First read through all full-length spans to get the button fill color, which will 6322 // be used as the background color for ensuring contrast of non-full-length spans. 6323 for (Object span : spans) { 6324 int spanStart = ss.getSpanStart(span); 6325 int spanEnd = ss.getSpanEnd(span); 6326 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 6327 if (!fullLength) { 6328 continue; 6329 } 6330 if (span instanceof TextAppearanceSpan) { 6331 TextAppearanceSpan originalSpan = (TextAppearanceSpan) span; 6332 ColorStateList textColor = originalSpan.getTextColor(); 6333 if (textColor != null) { 6334 result = textColor.getDefaultColor(); 6335 } 6336 } else if (span instanceof ForegroundColorSpan) { 6337 ForegroundColorSpan originalSpan = (ForegroundColorSpan) span; 6338 result = originalSpan.getForegroundColor(); 6339 } 6340 } 6341 } 6342 return result; 6343 } 6344 6345 /** 6346 * Ensures contrast on color spans against a background color. 6347 * Note that any full-length color spans will be removed instead of being contrasted. 6348 * 6349 * @param charSequence the charSequence on which the spans are 6350 * @param background the background color to ensure the contrast against 6351 * @return the contrasted charSequence 6352 * @hide 6353 */ 6354 @VisibleForTesting ensureColorSpanContrast(CharSequence charSequence, int background)6355 public static CharSequence ensureColorSpanContrast(CharSequence charSequence, 6356 int background) { 6357 if (charSequence instanceof Spanned) { 6358 Spanned ss = (Spanned) charSequence; 6359 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 6360 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 6361 for (Object span : spans) { 6362 Object resultSpan = span; 6363 int spanStart = ss.getSpanStart(span); 6364 int spanEnd = ss.getSpanEnd(span); 6365 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 6366 if (resultSpan instanceof CharacterStyle) { 6367 resultSpan = ((CharacterStyle) span).getUnderlying(); 6368 } 6369 if (resultSpan instanceof TextAppearanceSpan) { 6370 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 6371 ColorStateList textColor = originalSpan.getTextColor(); 6372 if (textColor != null) { 6373 if (fullLength) { 6374 // Let's drop the color from the span 6375 textColor = null; 6376 } else { 6377 int[] colors = textColor.getColors(); 6378 int[] newColors = new int[colors.length]; 6379 for (int i = 0; i < newColors.length; i++) { 6380 boolean isBgDark = isColorDark(background); 6381 newColors[i] = ContrastColorUtil.ensureLargeTextContrast( 6382 colors[i], background, isBgDark); 6383 } 6384 textColor = new ColorStateList(textColor.getStates().clone(), 6385 newColors); 6386 } 6387 resultSpan = new TextAppearanceSpan( 6388 originalSpan.getFamily(), 6389 originalSpan.getTextStyle(), 6390 originalSpan.getTextSize(), 6391 textColor, 6392 originalSpan.getLinkTextColor()); 6393 } 6394 } else if (resultSpan instanceof ForegroundColorSpan) { 6395 if (fullLength) { 6396 resultSpan = null; 6397 } else { 6398 ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; 6399 int foregroundColor = originalSpan.getForegroundColor(); 6400 boolean isBgDark = isColorDark(background); 6401 foregroundColor = ContrastColorUtil.ensureLargeTextContrast( 6402 foregroundColor, background, isBgDark); 6403 resultSpan = new ForegroundColorSpan(foregroundColor); 6404 } 6405 } else { 6406 resultSpan = span; 6407 } 6408 if (resultSpan != null) { 6409 builder.setSpan(resultSpan, spanStart, spanEnd, ss.getSpanFlags(span)); 6410 } 6411 } 6412 return builder; 6413 } 6414 return charSequence; 6415 } 6416 6417 /** 6418 * Determines if the color is light or dark. Specifically, this is using the same metric as 6419 * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that 6420 * the direction of color shift is consistent. 6421 * 6422 * @param color the color to check 6423 * @return true if the color has higher contrast with white than black 6424 * @hide 6425 */ isColorDark(int color)6426 public static boolean isColorDark(int color) { 6427 // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint. 6428 return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474; 6429 } 6430 6431 /** 6432 * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue 6433 * as the original color, but is lightened or darkened depending on whether the background 6434 * is dark or light. 6435 * 6436 * @hide 6437 */ 6438 @VisibleForTesting ensureButtonFillContrast(int color, int bg)6439 public static int ensureButtonFillContrast(int color, int bg) { 6440 return isColorDark(bg) 6441 ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3) 6442 : ContrastColorUtil.findContrastColor(color, bg, true, 1.3); 6443 } 6444 6445 6446 /** 6447 * @return Whether we are currently building a notification from a legacy (an app that 6448 * doesn't create material notifications by itself) app. 6449 */ isLegacy()6450 private boolean isLegacy() { 6451 if (!mIsLegacyInitialized) { 6452 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion 6453 < Build.VERSION_CODES.LOLLIPOP; 6454 mIsLegacyInitialized = true; 6455 } 6456 return mIsLegacy; 6457 } 6458 6459 private CharSequence processLegacyText(CharSequence charSequence) { 6460 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion(); 6461 if (isAlreadyLightText) { 6462 return getColorUtil().invertCharSequenceColors(charSequence); 6463 } else { 6464 return charSequence; 6465 } 6466 } 6467 6468 /** 6469 * Apply any necessary colors to the small icon 6470 */ 6471 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, 6472 StandardTemplateParams p) { 6473 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon); 6474 int color = getSmallIconColor(p); 6475 contentView.setInt(R.id.icon, "setBackgroundColor", 6476 getBackgroundColor(p)); 6477 contentView.setInt(R.id.icon, "setOriginalIconColor", 6478 colorable ? color : COLOR_INVALID); 6479 } 6480 6481 /** 6482 * Make the largeIcon dark if it's a fake smallIcon (that is, 6483 * if it's grayscale). 6484 */ 6485 // TODO: also check bounds, transparency, that sort of thing. 6486 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView, 6487 StandardTemplateParams p) { 6488 if (largeIcon != null && isLegacy() 6489 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 6490 // resolve color will fall back to the default when legacy 6491 int color = getSmallIconColor(p); 6492 contentView.setInt(R.id.icon, "setOriginalIconColor", color); 6493 } 6494 } 6495 6496 private void sanitizeColor() { 6497 if (mN.color != COLOR_DEFAULT) { 6498 mN.color |= 0xFF000000; // no alpha for custom colors 6499 } 6500 } 6501 6502 /** 6503 * Gets the standard action button color 6504 */ 6505 private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { 6506 return mTintActionButtons || isBackgroundColorized(p) 6507 ? getPrimaryAccentColor(p) : getSecondaryTextColor(p); 6508 } 6509 6510 /** 6511 * Gets the foreground color of the small icon. If the notification is colorized, this 6512 * is the primary text color, otherwise it's the contrast-adjusted app-provided color. 6513 */ 6514 private @ColorInt int getSmallIconColor(StandardTemplateParams p) { 6515 return getColors(p).getContrastColor(); 6516 } 6517 6518 /** @return the theme's accent color for colored UI elements. */ 6519 private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) { 6520 return getColors(p).getPrimaryAccentColor(); 6521 } 6522 6523 /** 6524 * Apply the unstyled operations and return a new {@link Notification} object. 6525 * @hide 6526 */ 6527 @NonNull 6528 public Notification buildUnstyled() { 6529 if (mActions.size() > 0) { 6530 mN.actions = new Action[mActions.size()]; 6531 mActions.toArray(mN.actions); 6532 } 6533 if (!mPersonList.isEmpty()) { 6534 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); 6535 } 6536 if (mN.bigContentView != null || mN.contentView != null 6537 || mN.headsUpContentView != null) { 6538 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 6539 } 6540 return mN; 6541 } 6542 6543 /** 6544 * Creates a Builder from an existing notification so further changes can be made. 6545 * @param context The context for your application / activity. 6546 * @param n The notification to create a Builder from. 6547 */ 6548 @NonNull recoverBuilder(Context context, Notification n)6549 public static Notification.Builder recoverBuilder(Context context, Notification n) { 6550 // Re-create notification context so we can access app resources. 6551 ApplicationInfo applicationInfo = n.extras.getParcelable( 6552 EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class); 6553 Context builderContext; 6554 if (applicationInfo != null) { 6555 try { 6556 builderContext = context.createApplicationContext(applicationInfo, 6557 Context.CONTEXT_RESTRICTED); 6558 } catch (NameNotFoundException e) { 6559 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 6560 builderContext = context; // try with our context 6561 } 6562 } else { 6563 builderContext = context; // try with given context 6564 } 6565 6566 return new Builder(builderContext, n); 6567 } 6568 6569 /** 6570 * Determines whether the platform can generate contextual actions for a notification. 6571 * By default this is true. 6572 */ 6573 @NonNull setAllowSystemGeneratedContextualActions(boolean allowed)6574 public Builder setAllowSystemGeneratedContextualActions(boolean allowed) { 6575 mN.mAllowSystemGeneratedContextualActions = allowed; 6576 return this; 6577 } 6578 6579 /** 6580 * @deprecated Use {@link #build()} instead. 6581 */ 6582 @Deprecated getNotification()6583 public Notification getNotification() { 6584 return build(); 6585 } 6586 6587 /** 6588 * Combine all of the options that have been set and return a new {@link Notification} 6589 * object. 6590 * 6591 * If this notification has {@link BubbleMetadata} attached that was created with 6592 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 6593 * metadata matches the shortcutId set on the notification builder, if one was set. 6594 * If the shortcutId's were specified but do not match, an exception is thrown here. 6595 * 6596 * @see BubbleMetadata.Builder#Builder(String) 6597 * @see #setShortcutId(String) 6598 */ 6599 @NonNull build()6600 public Notification build() { 6601 // Check shortcut id matches 6602 if (mN.mShortcutId != null 6603 && mN.mBubbleMetadata != null 6604 && mN.mBubbleMetadata.getShortcutId() != null 6605 && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { 6606 throw new IllegalArgumentException( 6607 "Notification and BubbleMetadata shortcut id's don't match," 6608 + " notification: " + mN.mShortcutId 6609 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); 6610 } 6611 6612 // first, add any extras from the calling code 6613 if (mUserExtras != null) { 6614 mN.extras = getAllExtras(); 6615 } 6616 6617 mN.creationTime = System.currentTimeMillis(); 6618 6619 // lazy stuff from mContext; see comment in Builder(Context, Notification) 6620 Notification.addFieldsFromContext(mContext, mN); 6621 6622 buildUnstyled(); 6623 6624 if (mStyle != null) { 6625 mStyle.reduceImageSizes(mContext); 6626 mStyle.purgeResources(); 6627 mStyle.validate(mContext); 6628 mStyle.buildStyled(mN); 6629 } 6630 mN.reduceImageSizes(mContext); 6631 6632 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 6633 && !styleDisplaysCustomViewInline()) { 6634 RemoteViews newContentView = mN.contentView; 6635 RemoteViews newBigContentView = mN.bigContentView; 6636 RemoteViews newHeadsUpContentView = mN.headsUpContentView; 6637 if (newContentView == null) { 6638 newContentView = createContentView(); 6639 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 6640 newContentView.getSequenceNumber()); 6641 } 6642 if (newBigContentView == null) { 6643 newBigContentView = createBigContentView(); 6644 if (newBigContentView != null) { 6645 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 6646 newBigContentView.getSequenceNumber()); 6647 } 6648 } 6649 if (newHeadsUpContentView == null) { 6650 newHeadsUpContentView = createHeadsUpContentView(); 6651 if (newHeadsUpContentView != null) { 6652 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 6653 newHeadsUpContentView.getSequenceNumber()); 6654 } 6655 } 6656 // Don't set any of the content views until after they have all been generated, 6657 // to avoid the generated .contentView triggering the logic which skips generating 6658 // the .bigContentView. 6659 mN.contentView = newContentView; 6660 mN.bigContentView = newBigContentView; 6661 mN.headsUpContentView = newHeadsUpContentView; 6662 } 6663 6664 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 6665 mN.flags |= FLAG_SHOW_LIGHTS; 6666 } 6667 6668 mN.allPendingIntents = null; 6669 6670 return mN; 6671 } 6672 styleDisplaysCustomViewInline()6673 private boolean styleDisplaysCustomViewInline() { 6674 return mStyle != null && mStyle.displayCustomViewInline(); 6675 } 6676 6677 /** 6678 * Apply this Builder to an existing {@link Notification} object. 6679 * 6680 * @hide 6681 */ 6682 @NonNull buildInto(@onNull Notification n)6683 public Notification buildInto(@NonNull Notification n) { 6684 build().cloneInto(n, true); 6685 return n; 6686 } 6687 6688 /** 6689 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 6690 * change. 6691 * 6692 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 6693 * 6694 * @hide 6695 */ maybeCloneStrippedForDelivery(Notification n)6696 public static Notification maybeCloneStrippedForDelivery(Notification n) { 6697 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 6698 6699 // Only strip views for known Styles because we won't know how to 6700 // re-create them otherwise. 6701 if (!TextUtils.isEmpty(templateClass) 6702 && getNotificationStyleClass(templateClass) == null) { 6703 return n; 6704 } 6705 6706 // Only strip unmodified BuilderRemoteViews. 6707 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 6708 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 6709 n.contentView.getSequenceNumber(); 6710 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 6711 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 6712 n.bigContentView.getSequenceNumber(); 6713 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 6714 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 6715 n.headsUpContentView.getSequenceNumber(); 6716 6717 // Nothing to do here, no need to clone. 6718 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 6719 return n; 6720 } 6721 6722 Notification clone = n.clone(); 6723 if (stripContentView) { 6724 clone.contentView = null; 6725 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 6726 } 6727 if (stripBigContentView) { 6728 clone.bigContentView = null; 6729 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 6730 } 6731 if (stripHeadsUpContentView) { 6732 clone.headsUpContentView = null; 6733 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 6734 } 6735 return clone; 6736 } 6737 6738 @UnsupportedAppUsage getBaseLayoutResource()6739 private int getBaseLayoutResource() { 6740 return R.layout.notification_template_material_base; 6741 } 6742 getHeadsUpBaseLayoutResource()6743 private int getHeadsUpBaseLayoutResource() { 6744 return R.layout.notification_template_material_heads_up_base; 6745 } 6746 getBigBaseLayoutResource()6747 private int getBigBaseLayoutResource() { 6748 return R.layout.notification_template_material_big_base; 6749 } 6750 getBigPictureLayoutResource()6751 private int getBigPictureLayoutResource() { 6752 return R.layout.notification_template_material_big_picture; 6753 } 6754 getBigTextLayoutResource()6755 private int getBigTextLayoutResource() { 6756 return R.layout.notification_template_material_big_text; 6757 } 6758 getInboxLayoutResource()6759 private int getInboxLayoutResource() { 6760 return R.layout.notification_template_material_inbox; 6761 } 6762 getMessagingLayoutResource()6763 private int getMessagingLayoutResource() { 6764 return R.layout.notification_template_material_messaging; 6765 } 6766 getBigMessagingLayoutResource()6767 private int getBigMessagingLayoutResource() { 6768 return R.layout.notification_template_material_big_messaging; 6769 } 6770 getConversationLayoutResource()6771 private int getConversationLayoutResource() { 6772 return R.layout.notification_template_material_conversation; 6773 } 6774 getActionLayoutResource()6775 private int getActionLayoutResource() { 6776 return R.layout.notification_material_action; 6777 } 6778 getEmphasizedActionLayoutResource()6779 private int getEmphasizedActionLayoutResource() { 6780 return R.layout.notification_material_action_emphasized; 6781 } 6782 getEmphasizedTombstoneActionLayoutResource()6783 private int getEmphasizedTombstoneActionLayoutResource() { 6784 return R.layout.notification_material_action_emphasized_tombstone; 6785 } 6786 getActionTombstoneLayoutResource()6787 private int getActionTombstoneLayoutResource() { 6788 return R.layout.notification_material_action_tombstone; 6789 } 6790 getBackgroundColor(StandardTemplateParams p)6791 private @ColorInt int getBackgroundColor(StandardTemplateParams p) { 6792 return getColors(p).getBackgroundColor(); 6793 } 6794 textColorsNeedInversion()6795 private boolean textColorsNeedInversion() { 6796 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { 6797 return false; 6798 } 6799 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 6800 return targetSdkVersion > Build.VERSION_CODES.M 6801 && targetSdkVersion < Build.VERSION_CODES.O; 6802 } 6803 6804 /** 6805 * Get the text that should be displayed in the statusBar when heads upped. This is 6806 * usually just the app name, but may be different depending on the style. 6807 * 6808 * @param publicMode If true, return a text that is safe to display in public. 6809 * 6810 * @hide 6811 */ getHeadsUpStatusBarText(boolean publicMode)6812 public CharSequence getHeadsUpStatusBarText(boolean publicMode) { 6813 if (mStyle != null && !publicMode) { 6814 CharSequence text = mStyle.getHeadsUpStatusBarText(); 6815 if (!TextUtils.isEmpty(text)) { 6816 return text; 6817 } 6818 } 6819 return loadHeaderAppName(); 6820 } 6821 6822 /** 6823 * @return if this builder uses a template 6824 * 6825 * @hide 6826 */ usesTemplate()6827 public boolean usesTemplate() { 6828 return (mN.contentView == null && mN.headsUpContentView == null 6829 && mN.bigContentView == null) 6830 || styleDisplaysCustomViewInline(); 6831 } 6832 } 6833 6834 /** 6835 * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom 6836 * remote views. 6837 * 6838 * @hide 6839 */ reduceImageSizes(Context context)6840 void reduceImageSizes(Context context) { 6841 if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) { 6842 return; 6843 } 6844 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 6845 6846 if (mSmallIcon != null 6847 // Only bitmap icons can be downscaled. 6848 && (mSmallIcon.getType() == Icon.TYPE_BITMAP 6849 || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) { 6850 Resources resources = context.getResources(); 6851 int maxSize = resources.getDimensionPixelSize( 6852 isLowRam ? R.dimen.notification_small_icon_size_low_ram 6853 : R.dimen.notification_small_icon_size); 6854 mSmallIcon.scaleDownIfNecessary(maxSize, maxSize); 6855 } 6856 6857 if (mLargeIcon != null || largeIcon != null) { 6858 Resources resources = context.getResources(); 6859 Class<? extends Style> style = getNotificationStyle(); 6860 int maxSize = resources.getDimensionPixelSize(isLowRam 6861 ? R.dimen.notification_right_icon_size_low_ram 6862 : R.dimen.notification_right_icon_size); 6863 if (mLargeIcon != null) { 6864 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize); 6865 } 6866 if (largeIcon != null) { 6867 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize); 6868 } 6869 } 6870 reduceImageSizesForRemoteView(contentView, context, isLowRam); 6871 reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam); 6872 reduceImageSizesForRemoteView(bigContentView, context, isLowRam); 6873 extras.putBoolean(EXTRA_REDUCED_IMAGES, true); 6874 } 6875 reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)6876 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, 6877 boolean isLowRam) { 6878 if (remoteView != null) { 6879 Resources resources = context.getResources(); 6880 int maxWidth = resources.getDimensionPixelSize(isLowRam 6881 ? R.dimen.notification_custom_view_max_image_width_low_ram 6882 : R.dimen.notification_custom_view_max_image_width); 6883 int maxHeight = resources.getDimensionPixelSize(isLowRam 6884 ? R.dimen.notification_custom_view_max_image_height_low_ram 6885 : R.dimen.notification_custom_view_max_image_height); 6886 remoteView.reduceImageSizes(maxWidth, maxHeight); 6887 } 6888 } 6889 6890 /** 6891 * @return whether this notification is a foreground service notification 6892 * @hide 6893 */ isForegroundService()6894 public boolean isForegroundService() { 6895 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 6896 } 6897 6898 /** 6899 * Describe whether this notification's content such that it should always display 6900 * immediately when tied to a foreground service, even if the system might generally 6901 * avoid showing the notifications for short-lived foreground service lifetimes. 6902 * 6903 * Immediate visibility of the Notification is indicated when: 6904 * <ul> 6905 * <li>The app specifically indicated it with 6906 * {@link Notification.Builder#setForegroundServiceBehavior(int) 6907 * setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li> 6908 * <li>It is a media notification or has an associated media session</li> 6909 * <li>It is a call or navigation notification</li> 6910 * <li>It provides additional action affordances</li> 6911 * </ul> 6912 * 6913 * If the app has specified 6914 * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)} 6915 * then this method will return {@code false} and notification visibility will be 6916 * deferred following the service's transition to the foreground state even in the 6917 * circumstances described above. 6918 * 6919 * @return whether this notification should be displayed immediately when 6920 * its associated service transitions to the foreground state 6921 * @hide 6922 */ 6923 @TestApi shouldShowForegroundImmediately()6924 public boolean shouldShowForegroundImmediately() { 6925 // Has the app demanded immediate display? 6926 if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) { 6927 return true; 6928 } 6929 6930 // Has the app demanded deferred display? 6931 if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) { 6932 return false; 6933 } 6934 6935 // We show these sorts of notifications immediately in the absence of 6936 // any explicit app declaration 6937 if (isMediaNotification() 6938 || CATEGORY_CALL.equals(category) 6939 || CATEGORY_NAVIGATION.equals(category) 6940 || (actions != null && actions.length > 0)) { 6941 return true; 6942 } 6943 6944 // No extenuating circumstances: defer visibility 6945 return false; 6946 } 6947 6948 /** 6949 * Has forced deferral for FGS purposes been specified? 6950 * @hide 6951 */ isForegroundDisplayForceDeferred()6952 public boolean isForegroundDisplayForceDeferred() { 6953 return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior; 6954 } 6955 6956 /** 6957 * @return the style class of this notification 6958 * @hide 6959 */ getNotificationStyle()6960 public Class<? extends Notification.Style> getNotificationStyle() { 6961 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6962 6963 if (!TextUtils.isEmpty(templateClass)) { 6964 return Notification.getNotificationStyleClass(templateClass); 6965 } 6966 return null; 6967 } 6968 6969 /** 6970 * @return whether the style of this notification is the one provided 6971 * @hide 6972 */ isStyle(@onNull Class<? extends Style> styleClass)6973 public boolean isStyle(@NonNull Class<? extends Style> styleClass) { 6974 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 6975 return Objects.equals(templateClass, styleClass.getName()); 6976 } 6977 6978 /** 6979 * @return true if this notification is colorized *for the purposes of ranking*. If the 6980 * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual 6981 * appearance of the notification may not be "colorized". 6982 * 6983 * @hide 6984 */ isColorized()6985 public boolean isColorized() { 6986 return extras.getBoolean(EXTRA_COLORIZED) 6987 && (hasColorizedPermission() || isForegroundService()); 6988 } 6989 6990 /** 6991 * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS 6992 * permission. The permission is checked when a notification is enqueued. 6993 * 6994 * @hide 6995 */ hasColorizedPermission()6996 public boolean hasColorizedPermission() { 6997 return (flags & Notification.FLAG_CAN_COLORIZE) != 0; 6998 } 6999 7000 /** 7001 * @return true if this is a media style notification with a media session 7002 * 7003 * @hide 7004 */ isMediaNotification()7005 public boolean isMediaNotification() { 7006 Class<? extends Style> style = getNotificationStyle(); 7007 boolean isMediaStyle = (MediaStyle.class.equals(style) 7008 || DecoratedMediaCustomViewStyle.class.equals(style)); 7009 7010 boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION, 7011 MediaSession.Token.class) != null; 7012 7013 return isMediaStyle && hasMediaSession; 7014 } 7015 7016 /** 7017 * @return true if this notification is showing as a bubble 7018 * 7019 * @hide 7020 */ isBubbleNotification()7021 public boolean isBubbleNotification() { 7022 return (flags & Notification.FLAG_BUBBLE) != 0; 7023 } 7024 hasLargeIcon()7025 private boolean hasLargeIcon() { 7026 return mLargeIcon != null || largeIcon != null; 7027 } 7028 7029 /** 7030 * @return true if the notification will show the time; false otherwise 7031 * @hide 7032 */ showsTime()7033 public boolean showsTime() { 7034 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 7035 } 7036 7037 /** 7038 * @return true if the notification will show a chronometer; false otherwise 7039 * @hide 7040 */ showsChronometer()7041 public boolean showsChronometer() { 7042 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 7043 } 7044 7045 /** 7046 * @return true if the notification has image 7047 */ hasImage()7048 public boolean hasImage() { 7049 if (isStyle(MessagingStyle.class) && extras != null) { 7050 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 7051 if (!ArrayUtils.isEmpty(messages)) { 7052 for (MessagingStyle.Message m : MessagingStyle.Message 7053 .getMessagesFromBundleArray(messages)) { 7054 if (m.getDataUri() != null 7055 && m.getDataMimeType() != null 7056 && m.getDataMimeType().startsWith("image/")) { 7057 return true; 7058 } 7059 } 7060 } 7061 } else if (hasLargeIcon()) { 7062 return true; 7063 } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 7064 return true; 7065 } 7066 return false; 7067 } 7068 7069 7070 /** 7071 * @removed 7072 */ 7073 @SystemApi getNotificationStyleClass(String templateClass)7074 public static Class<? extends Style> getNotificationStyleClass(String templateClass) { 7075 for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) { 7076 if (templateClass.equals(innerClass.getName())) { 7077 return innerClass; 7078 } 7079 } 7080 return null; 7081 } 7082 buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)7083 private static void buildCustomContentIntoTemplate(@NonNull Context context, 7084 @NonNull RemoteViews template, @Nullable RemoteViews customContent, 7085 @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) { 7086 int childIndex = -1; 7087 if (customContent != null) { 7088 // Need to clone customContent before adding, because otherwise it can no longer be 7089 // parceled independently of remoteViews. 7090 customContent = customContent.clone(); 7091 if (p.mHeaderless) { 7092 template.removeFromParent(R.id.notification_top_line); 7093 // We do not know how many lines ar emote view has, so we presume it has 2; this 7094 // ensures that we don't under-pad the content, which could lead to abuse, at the 7095 // cost of making single-line custom content over-padded. 7096 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */); 7097 } else { 7098 // also update the end margin to account for the large icon or expander 7099 Resources resources = context.getResources(); 7100 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column, 7101 resources.getDimension(R.dimen.notification_content_margin_end) 7102 / resources.getDisplayMetrics().density); 7103 } 7104 template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 7105 template.addView(R.id.notification_main_column, customContent, 0 /* index */); 7106 template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 7107 childIndex = 0; 7108 } 7109 template.setIntTag(R.id.notification_main_column, 7110 com.android.internal.R.id.notification_custom_view_index_tag, 7111 childIndex); 7112 } 7113 7114 /** 7115 * An object that can apply a rich notification style to a {@link Notification.Builder} 7116 * object. 7117 */ 7118 public static abstract class Style { 7119 7120 /** 7121 * The number of items allowed simulatanously in the remote input history. 7122 * @hide 7123 */ 7124 static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3; 7125 private CharSequence mBigContentTitle; 7126 7127 /** 7128 * @hide 7129 */ 7130 protected CharSequence mSummaryText = null; 7131 7132 /** 7133 * @hide 7134 */ 7135 protected boolean mSummaryTextSet = false; 7136 7137 protected Builder mBuilder; 7138 7139 /** 7140 * Overrides ContentTitle in the big form of the template. 7141 * This defaults to the value passed to setContentTitle(). 7142 */ internalSetBigContentTitle(CharSequence title)7143 protected void internalSetBigContentTitle(CharSequence title) { 7144 mBigContentTitle = title; 7145 } 7146 7147 /** 7148 * Set the first line of text after the detail section in the big form of the template. 7149 */ internalSetSummaryText(CharSequence cs)7150 protected void internalSetSummaryText(CharSequence cs) { 7151 mSummaryText = cs; 7152 mSummaryTextSet = true; 7153 } 7154 setBuilder(Builder builder)7155 public void setBuilder(Builder builder) { 7156 if (mBuilder != builder) { 7157 mBuilder = builder; 7158 if (mBuilder != null) { 7159 mBuilder.setStyle(this); 7160 } 7161 } 7162 } 7163 checkBuilder()7164 protected void checkBuilder() { 7165 if (mBuilder == null) { 7166 throw new IllegalArgumentException("Style requires a valid Builder object"); 7167 } 7168 } 7169 getStandardView(int layoutId)7170 protected RemoteViews getStandardView(int layoutId) { 7171 // TODO(jeffdq): set the view type based on the layout resource? 7172 StandardTemplateParams p = mBuilder.mParams.reset() 7173 .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED) 7174 .fillTextsFrom(mBuilder); 7175 return getStandardView(layoutId, p, null); 7176 } 7177 7178 7179 /** 7180 * Get the standard view for this style. 7181 * 7182 * @param layoutId The layout id to use. 7183 * @param p the params for this inflation. 7184 * @param result The result where template bind information is saved. 7185 * @return A remoteView for this style. 7186 * @hide 7187 */ getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7188 protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p, 7189 TemplateBindResult result) { 7190 checkBuilder(); 7191 7192 if (mBigContentTitle != null) { 7193 p.title = mBigContentTitle; 7194 } 7195 7196 return mBuilder.applyStandardTemplateWithActions(layoutId, p, result); 7197 } 7198 7199 /** 7200 * Construct a Style-specific RemoteViews for the collapsed notification layout. 7201 * The default implementation has nothing additional to add. 7202 * 7203 * @param increasedHeight true if this layout be created with an increased height. 7204 * @hide 7205 */ makeContentView(boolean increasedHeight)7206 public RemoteViews makeContentView(boolean increasedHeight) { 7207 return null; 7208 } 7209 7210 /** 7211 * Construct a Style-specific RemoteViews for the final big notification layout. 7212 * @hide 7213 */ makeBigContentView()7214 public RemoteViews makeBigContentView() { 7215 return null; 7216 } 7217 7218 /** 7219 * Construct a Style-specific RemoteViews for the final HUN layout. 7220 * 7221 * @param increasedHeight true if this layout be created with an increased height. 7222 * @hide 7223 */ makeHeadsUpContentView(boolean increasedHeight)7224 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7225 return null; 7226 } 7227 7228 /** 7229 * Apply any style-specific extras to this notification before shipping it out. 7230 * @hide 7231 */ addExtras(Bundle extras)7232 public void addExtras(Bundle extras) { 7233 if (mSummaryTextSet) { 7234 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 7235 } 7236 if (mBigContentTitle != null) { 7237 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 7238 } 7239 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 7240 } 7241 7242 /** 7243 * Reconstruct the internal state of this Style object from extras. 7244 * @hide 7245 */ restoreFromExtras(Bundle extras)7246 protected void restoreFromExtras(Bundle extras) { 7247 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 7248 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 7249 mSummaryTextSet = true; 7250 } 7251 if (extras.containsKey(EXTRA_TITLE_BIG)) { 7252 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 7253 } 7254 } 7255 7256 7257 /** 7258 * @hide 7259 */ buildStyled(Notification wip)7260 public Notification buildStyled(Notification wip) { 7261 addExtras(wip.extras); 7262 return wip; 7263 } 7264 7265 /** 7266 * @hide 7267 */ purgeResources()7268 public void purgeResources() {} 7269 7270 /** 7271 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 7272 * attached to. 7273 * 7274 * @return the fully constructed Notification. 7275 */ build()7276 public Notification build() { 7277 checkBuilder(); 7278 return mBuilder.build(); 7279 } 7280 7281 /** 7282 * @hide 7283 * @return Whether we should put the summary be put into the notification header 7284 */ hasSummaryInHeader()7285 public boolean hasSummaryInHeader() { 7286 return true; 7287 } 7288 7289 /** 7290 * @hide 7291 * @return Whether custom content views are displayed inline in the style 7292 */ displayCustomViewInline()7293 public boolean displayCustomViewInline() { 7294 return false; 7295 } 7296 7297 /** 7298 * Reduces the image sizes contained in this style. 7299 * 7300 * @hide 7301 */ reduceImageSizes(Context context)7302 public void reduceImageSizes(Context context) { 7303 } 7304 7305 /** 7306 * Validate that this style was properly composed. This is called at build time. 7307 * @hide 7308 */ validate(Context context)7309 public void validate(Context context) { 7310 } 7311 7312 /** 7313 * @hide 7314 */ areNotificationsVisiblyDifferent(Style other)7315 public abstract boolean areNotificationsVisiblyDifferent(Style other); 7316 7317 /** 7318 * @return the text that should be displayed in the statusBar when heads-upped. 7319 * If {@code null} is returned, the default implementation will be used. 7320 * 7321 * @hide 7322 */ getHeadsUpStatusBarText()7323 public CharSequence getHeadsUpStatusBarText() { 7324 return null; 7325 } 7326 } 7327 7328 /** 7329 * Helper class for generating large-format notifications that include a large image attachment. 7330 * 7331 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 7332 * <pre class="prettyprint"> 7333 * Notification notif = new Notification.Builder(mContext) 7334 * .setContentTitle("New photo from " + sender.toString()) 7335 * .setContentText(subject) 7336 * .setSmallIcon(R.drawable.new_post) 7337 * .setLargeIcon(aBitmap) 7338 * .setStyle(new Notification.BigPictureStyle() 7339 * .bigPicture(aBigBitmap)) 7340 * .build(); 7341 * </pre> 7342 * 7343 * @see Notification#bigContentView 7344 */ 7345 public static class BigPictureStyle extends Style { 7346 private Icon mPictureIcon; 7347 private Icon mBigLargeIcon; 7348 private boolean mBigLargeIconSet = false; 7349 private CharSequence mPictureContentDescription; 7350 private boolean mShowBigPictureWhenCollapsed; 7351 BigPictureStyle()7352 public BigPictureStyle() { 7353 } 7354 7355 /** 7356 * @deprecated use {@code BigPictureStyle()}. 7357 */ 7358 @Deprecated BigPictureStyle(Builder builder)7359 public BigPictureStyle(Builder builder) { 7360 setBuilder(builder); 7361 } 7362 7363 /** 7364 * Overrides ContentTitle in the big form of the template. 7365 * This defaults to the value passed to setContentTitle(). 7366 */ 7367 @NonNull setBigContentTitle(@ullable CharSequence title)7368 public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) { 7369 internalSetBigContentTitle(safeCharSequence(title)); 7370 return this; 7371 } 7372 7373 /** 7374 * Set the first line of text after the detail section in the big form of the template. 7375 */ 7376 @NonNull setSummaryText(@ullable CharSequence cs)7377 public BigPictureStyle setSummaryText(@Nullable CharSequence cs) { 7378 internalSetSummaryText(safeCharSequence(cs)); 7379 return this; 7380 } 7381 7382 /** 7383 * Set the content description of the big picture. 7384 */ 7385 @NonNull setContentDescription( @ullable CharSequence contentDescription)7386 public BigPictureStyle setContentDescription( 7387 @Nullable CharSequence contentDescription) { 7388 mPictureContentDescription = contentDescription; 7389 return this; 7390 } 7391 7392 /** 7393 * @hide 7394 */ 7395 @Nullable getBigPicture()7396 public Icon getBigPicture() { 7397 if (mPictureIcon != null) { 7398 return mPictureIcon; 7399 } 7400 return null; 7401 } 7402 7403 /** 7404 * Provide the bitmap to be used as the payload for the BigPicture notification. 7405 */ 7406 @NonNull bigPicture(@ullable Bitmap b)7407 public BigPictureStyle bigPicture(@Nullable Bitmap b) { 7408 mPictureIcon = b == null ? null : Icon.createWithBitmap(b); 7409 return this; 7410 } 7411 7412 /** 7413 * Provide the content Uri to be used as the payload for the BigPicture notification. 7414 */ 7415 @NonNull bigPicture(@ullable Icon icon)7416 public BigPictureStyle bigPicture(@Nullable Icon icon) { 7417 mPictureIcon = icon; 7418 return this; 7419 } 7420 7421 /** 7422 * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and 7423 * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed 7424 * state of this notification. 7425 */ 7426 @NonNull showBigPictureWhenCollapsed(boolean show)7427 public BigPictureStyle showBigPictureWhenCollapsed(boolean show) { 7428 mShowBigPictureWhenCollapsed = show; 7429 return this; 7430 } 7431 7432 /** 7433 * Override the large icon when the big notification is shown. 7434 */ 7435 @NonNull bigLargeIcon(@ullable Bitmap b)7436 public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) { 7437 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 7438 } 7439 7440 /** 7441 * Override the large icon when the big notification is shown. 7442 */ 7443 @NonNull bigLargeIcon(@ullable Icon icon)7444 public BigPictureStyle bigLargeIcon(@Nullable Icon icon) { 7445 mBigLargeIconSet = true; 7446 mBigLargeIcon = icon; 7447 return this; 7448 } 7449 7450 /** @hide */ 7451 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 7452 7453 /** 7454 * @hide 7455 */ 7456 @Override purgeResources()7457 public void purgeResources() { 7458 super.purgeResources(); 7459 if (mPictureIcon != null) { 7460 mPictureIcon.convertToAshmem(); 7461 } 7462 if (mBigLargeIcon != null) { 7463 mBigLargeIcon.convertToAshmem(); 7464 } 7465 } 7466 7467 /** 7468 * @hide 7469 */ 7470 @Override reduceImageSizes(Context context)7471 public void reduceImageSizes(Context context) { 7472 super.reduceImageSizes(context); 7473 Resources resources = context.getResources(); 7474 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 7475 if (mPictureIcon != null) { 7476 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam 7477 ? R.dimen.notification_big_picture_max_height_low_ram 7478 : R.dimen.notification_big_picture_max_height); 7479 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam 7480 ? R.dimen.notification_big_picture_max_width_low_ram 7481 : R.dimen.notification_big_picture_max_width); 7482 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight); 7483 } 7484 if (mBigLargeIcon != null) { 7485 int rightIconSize = resources.getDimensionPixelSize(isLowRam 7486 ? R.dimen.notification_right_icon_size_low_ram 7487 : R.dimen.notification_right_icon_size); 7488 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 7489 } 7490 } 7491 7492 /** 7493 * @hide 7494 */ 7495 @Override makeContentView(boolean increasedHeight)7496 public RemoteViews makeContentView(boolean increasedHeight) { 7497 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7498 return super.makeContentView(increasedHeight); 7499 } 7500 7501 StandardTemplateParams p = mBuilder.mParams.reset() 7502 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 7503 .fillTextsFrom(mBuilder) 7504 .promotedPicture(mPictureIcon); 7505 return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */); 7506 } 7507 7508 /** 7509 * @hide 7510 */ 7511 @Override makeHeadsUpContentView(boolean increasedHeight)7512 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7513 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 7514 return super.makeHeadsUpContentView(increasedHeight); 7515 } 7516 7517 StandardTemplateParams p = mBuilder.mParams.reset() 7518 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 7519 .fillTextsFrom(mBuilder) 7520 .promotedPicture(mPictureIcon); 7521 return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */); 7522 } 7523 7524 /** 7525 * @hide 7526 */ makeBigContentView()7527 public RemoteViews makeBigContentView() { 7528 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 7529 // This covers the following cases: 7530 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 7531 // mN.mLargeIcon 7532 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 7533 Icon oldLargeIcon = null; 7534 Bitmap largeIconLegacy = null; 7535 if (mBigLargeIconSet) { 7536 oldLargeIcon = mBuilder.mN.mLargeIcon; 7537 mBuilder.mN.mLargeIcon = mBigLargeIcon; 7538 // The legacy largeIcon might not allow us to clear the image, as it's taken in 7539 // replacement if the other one is null. Because we're restoring these legacy icons 7540 // for old listeners, this is in general non-null. 7541 largeIconLegacy = mBuilder.mN.largeIcon; 7542 mBuilder.mN.largeIcon = null; 7543 } 7544 7545 StandardTemplateParams p = mBuilder.mParams.reset() 7546 .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder); 7547 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), 7548 p, null /* result */); 7549 if (mSummaryTextSet) { 7550 contentView.setTextViewText(R.id.text, mBuilder.processTextSpans( 7551 mBuilder.processLegacyText(mSummaryText))); 7552 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); 7553 contentView.setViewVisibility(R.id.text, View.VISIBLE); 7554 } 7555 7556 if (mBigLargeIconSet) { 7557 mBuilder.mN.mLargeIcon = oldLargeIcon; 7558 mBuilder.mN.largeIcon = largeIconLegacy; 7559 } 7560 7561 contentView.setImageViewIcon(R.id.big_picture, mPictureIcon); 7562 7563 if (mPictureContentDescription != null) { 7564 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription); 7565 } 7566 7567 return contentView; 7568 } 7569 7570 /** 7571 * @hide 7572 */ addExtras(Bundle extras)7573 public void addExtras(Bundle extras) { 7574 super.addExtras(extras); 7575 7576 if (mBigLargeIconSet) { 7577 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 7578 } 7579 if (mPictureContentDescription != null) { 7580 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION, 7581 mPictureContentDescription); 7582 } 7583 extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); 7584 7585 // If the icon contains a bitmap, use the old extra so that listeners which look for 7586 // that extra can still find the picture. Don't include the new extra in that case, 7587 // to avoid duplicating data. 7588 if (mPictureIcon != null && mPictureIcon.getType() == Icon.TYPE_BITMAP) { 7589 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); 7590 extras.putParcelable(EXTRA_PICTURE_ICON, null); 7591 } else { 7592 extras.putParcelable(EXTRA_PICTURE, null); 7593 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); 7594 } 7595 } 7596 7597 /** 7598 * @hide 7599 */ 7600 @Override restoreFromExtras(Bundle extras)7601 protected void restoreFromExtras(Bundle extras) { 7602 super.restoreFromExtras(extras); 7603 7604 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 7605 mBigLargeIconSet = true; 7606 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class); 7607 } 7608 7609 if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) { 7610 mPictureContentDescription = 7611 extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION); 7612 } 7613 7614 mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED); 7615 7616 mPictureIcon = getPictureIcon(extras); 7617 } 7618 7619 /** @hide */ 7620 @Nullable getPictureIcon(@ullable Bundle extras)7621 public static Icon getPictureIcon(@Nullable Bundle extras) { 7622 if (extras == null) return null; 7623 // When this style adds a picture, we only add one of the keys. If both were added, 7624 // it would most likely be a legacy app trying to override the picture in some way. 7625 // Because of that case it's better to give precedence to the legacy field. 7626 Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class); 7627 if (bitmapPicture != null) { 7628 return Icon.createWithBitmap(bitmapPicture); 7629 } else { 7630 return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class); 7631 } 7632 } 7633 7634 /** 7635 * @hide 7636 */ 7637 @Override hasSummaryInHeader()7638 public boolean hasSummaryInHeader() { 7639 return false; 7640 } 7641 7642 /** 7643 * @hide 7644 * Note that we aren't actually comparing the contents of the bitmaps here, so this 7645 * is only doing a cursory inspection. Bitmaps of equal size will appear the same. 7646 */ 7647 @Override areNotificationsVisiblyDifferent(Style other)7648 public boolean areNotificationsVisiblyDifferent(Style other) { 7649 if (other == null || getClass() != other.getClass()) { 7650 return true; 7651 } 7652 BigPictureStyle otherS = (BigPictureStyle) other; 7653 return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture()); 7654 } 7655 areIconsObviouslyDifferent(Icon a, Icon b)7656 private static boolean areIconsObviouslyDifferent(Icon a, Icon b) { 7657 if (a == b) { 7658 return false; 7659 } 7660 if (a == null || b == null) { 7661 return true; 7662 } 7663 if (a.sameAs(b)) { 7664 return false; 7665 } 7666 final int aType = a.getType(); 7667 if (aType != b.getType()) { 7668 return true; 7669 } 7670 if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) { 7671 final Bitmap aBitmap = a.getBitmap(); 7672 final Bitmap bBitmap = b.getBitmap(); 7673 return aBitmap.getWidth() != bBitmap.getWidth() 7674 || aBitmap.getHeight() != bBitmap.getHeight() 7675 || aBitmap.getConfig() != bBitmap.getConfig() 7676 || aBitmap.getGenerationId() != bBitmap.getGenerationId(); 7677 } 7678 return true; 7679 } 7680 } 7681 7682 /** 7683 * Helper class for generating large-format notifications that include a lot of text. 7684 * 7685 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 7686 * <pre class="prettyprint"> 7687 * Notification notif = new Notification.Builder(mContext) 7688 * .setContentTitle("New mail from " + sender.toString()) 7689 * .setContentText(subject) 7690 * .setSmallIcon(R.drawable.new_mail) 7691 * .setLargeIcon(aBitmap) 7692 * .setStyle(new Notification.BigTextStyle() 7693 * .bigText(aVeryLongString)) 7694 * .build(); 7695 * </pre> 7696 * 7697 * @see Notification#bigContentView 7698 */ 7699 public static class BigTextStyle extends Style { 7700 7701 private CharSequence mBigText; 7702 BigTextStyle()7703 public BigTextStyle() { 7704 } 7705 7706 /** 7707 * @deprecated use {@code BigTextStyle()}. 7708 */ 7709 @Deprecated BigTextStyle(Builder builder)7710 public BigTextStyle(Builder builder) { 7711 setBuilder(builder); 7712 } 7713 7714 /** 7715 * Overrides ContentTitle in the big form of the template. 7716 * This defaults to the value passed to setContentTitle(). 7717 */ setBigContentTitle(CharSequence title)7718 public BigTextStyle setBigContentTitle(CharSequence title) { 7719 internalSetBigContentTitle(safeCharSequence(title)); 7720 return this; 7721 } 7722 7723 /** 7724 * Set the first line of text after the detail section in the big form of the template. 7725 */ setSummaryText(CharSequence cs)7726 public BigTextStyle setSummaryText(CharSequence cs) { 7727 internalSetSummaryText(safeCharSequence(cs)); 7728 return this; 7729 } 7730 7731 /** 7732 * Provide the longer text to be displayed in the big form of the 7733 * template in place of the content text. 7734 */ bigText(CharSequence cs)7735 public BigTextStyle bigText(CharSequence cs) { 7736 mBigText = safeCharSequence(cs); 7737 return this; 7738 } 7739 7740 /** 7741 * @hide 7742 */ getBigText()7743 public CharSequence getBigText() { 7744 return mBigText; 7745 } 7746 7747 /** 7748 * @hide 7749 */ addExtras(Bundle extras)7750 public void addExtras(Bundle extras) { 7751 super.addExtras(extras); 7752 7753 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 7754 } 7755 7756 /** 7757 * @hide 7758 */ 7759 @Override restoreFromExtras(Bundle extras)7760 protected void restoreFromExtras(Bundle extras) { 7761 super.restoreFromExtras(extras); 7762 7763 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 7764 } 7765 7766 /** 7767 * @param increasedHeight true if this layout be created with an increased height. 7768 * 7769 * @hide 7770 */ 7771 @Override makeContentView(boolean increasedHeight)7772 public RemoteViews makeContentView(boolean increasedHeight) { 7773 if (increasedHeight) { 7774 ArrayList<Action> originalActions = mBuilder.mActions; 7775 mBuilder.mActions = new ArrayList<>(); 7776 RemoteViews remoteViews = makeBigContentView(); 7777 mBuilder.mActions = originalActions; 7778 return remoteViews; 7779 } 7780 return super.makeContentView(increasedHeight); 7781 } 7782 7783 /** 7784 * @hide 7785 */ 7786 @Override makeHeadsUpContentView(boolean increasedHeight)7787 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7788 if (increasedHeight && mBuilder.mActions.size() > 0) { 7789 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP? 7790 return makeBigContentView(); 7791 } 7792 return super.makeHeadsUpContentView(increasedHeight); 7793 } 7794 7795 /** 7796 * @hide 7797 */ makeBigContentView()7798 public RemoteViews makeBigContentView() { 7799 StandardTemplateParams p = mBuilder.mParams.reset() 7800 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 7801 .allowTextWithProgress(true) 7802 .textViewId(R.id.big_text) 7803 .fillTextsFrom(mBuilder); 7804 7805 // Replace the text with the big text, but only if the big text is not empty. 7806 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 7807 if (!TextUtils.isEmpty(bigTextText)) { 7808 p.text(bigTextText); 7809 } 7810 7811 return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */); 7812 } 7813 7814 /** 7815 * @hide 7816 * Spans are ignored when comparing text for visual difference. 7817 */ 7818 @Override areNotificationsVisiblyDifferent(Style other)7819 public boolean areNotificationsVisiblyDifferent(Style other) { 7820 if (other == null || getClass() != other.getClass()) { 7821 return true; 7822 } 7823 BigTextStyle newS = (BigTextStyle) other; 7824 return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText())); 7825 } 7826 7827 } 7828 7829 /** 7830 * Helper class for generating large-format notifications that include multiple back-and-forth 7831 * messages of varying types between any number of people. 7832 * 7833 * <p> 7834 * If the platform does not provide large-format notifications, this method has no effect. The 7835 * user will always see the normal notification view. 7836 * 7837 * <p> 7838 * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is 7839 * required to use the {@link Person} class in order to get an optimal rendering of the 7840 * notification and its avatars. For conversations involving multiple people, the app should 7841 * also make sure that it marks the conversation as a group with 7842 * {@link #setGroupConversation(boolean)}. 7843 * 7844 * <p> 7845 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. 7846 * Here's an example of how this may be used: 7847 * <pre class="prettyprint"> 7848 * 7849 * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build(); 7850 * MessagingStyle style = new MessagingStyle(user) 7851 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson()) 7852 * .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson()) 7853 * .setGroupConversation(hasMultiplePeople()); 7854 * 7855 * Notification noti = new Notification.Builder() 7856 * .setContentTitle("2 new messages with " + sender.toString()) 7857 * .setContentText(subject) 7858 * .setSmallIcon(R.drawable.new_message) 7859 * .setLargeIcon(aBitmap) 7860 * .setStyle(style) 7861 * .build(); 7862 * </pre> 7863 */ 7864 public static class MessagingStyle extends Style { 7865 7866 /** 7867 * The maximum number of messages that will be retained in the Notification itself (the 7868 * number displayed is up to the platform). 7869 */ 7870 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 7871 7872 7873 /** @hide */ 7874 public static final int CONVERSATION_TYPE_LEGACY = 0; 7875 /** @hide */ 7876 public static final int CONVERSATION_TYPE_NORMAL = 1; 7877 /** @hide */ 7878 public static final int CONVERSATION_TYPE_IMPORTANT = 2; 7879 7880 /** @hide */ 7881 @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = { 7882 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT 7883 }) 7884 @Retention(RetentionPolicy.SOURCE) 7885 public @interface ConversationType {} 7886 7887 @NonNull Person mUser; 7888 @Nullable CharSequence mConversationTitle; 7889 @Nullable Icon mShortcutIcon; 7890 List<Message> mMessages = new ArrayList<>(); 7891 List<Message> mHistoricMessages = new ArrayList<>(); 7892 boolean mIsGroupConversation; 7893 @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; 7894 int mUnreadMessageCount; 7895 MessagingStyle()7896 MessagingStyle() { 7897 } 7898 7899 /** 7900 * @param userDisplayName Required - the name to be displayed for any replies sent by the 7901 * user before the posting app reposts the notification with those messages after they've 7902 * been actually sent and in previous messages sent by the user added in 7903 * {@link #addMessage(Notification.MessagingStyle.Message)} 7904 * 7905 * @deprecated use {@code MessagingStyle(Person)} 7906 */ MessagingStyle(@onNull CharSequence userDisplayName)7907 public MessagingStyle(@NonNull CharSequence userDisplayName) { 7908 this(new Person.Builder().setName(userDisplayName).build()); 7909 } 7910 7911 /** 7912 * @param user Required - The person displayed for any messages that are sent by the 7913 * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} 7914 * who don't have a Person associated with it will be displayed as if they were sent 7915 * by this user. The user also needs to have a valid name associated with it, which is 7916 * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}. 7917 */ MessagingStyle(@onNull Person user)7918 public MessagingStyle(@NonNull Person user) { 7919 mUser = user; 7920 } 7921 7922 /** 7923 * Validate that this style was properly composed. This is called at build time. 7924 * @hide 7925 */ 7926 @Override validate(Context context)7927 public void validate(Context context) { 7928 super.validate(context); 7929 if (context.getApplicationInfo().targetSdkVersion 7930 >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { 7931 throw new RuntimeException("User must be valid and have a name."); 7932 } 7933 } 7934 7935 /** 7936 * @return the text that should be displayed in the statusBar when heads upped. 7937 * If {@code null} is returned, the default implementation will be used. 7938 * 7939 * @hide 7940 */ 7941 @Override getHeadsUpStatusBarText()7942 public CharSequence getHeadsUpStatusBarText() { 7943 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 7944 ? super.mBigContentTitle 7945 : mConversationTitle; 7946 if (mConversationType == CONVERSATION_TYPE_LEGACY 7947 && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { 7948 return conversationTitle; 7949 } 7950 return null; 7951 } 7952 7953 /** 7954 * @return the user to be displayed for any replies sent by the user 7955 */ 7956 @NonNull getUser()7957 public Person getUser() { 7958 return mUser; 7959 } 7960 7961 /** 7962 * Returns the name to be displayed for any replies sent by the user 7963 * 7964 * @deprecated use {@link #getUser()} instead 7965 */ getUserDisplayName()7966 public CharSequence getUserDisplayName() { 7967 return mUser.getName(); 7968 } 7969 7970 /** 7971 * Sets the title to be displayed on this conversation. May be set to {@code null}. 7972 * 7973 * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored 7974 * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. 7975 * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing, 7976 * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title 7977 * instead. 7978 * 7979 * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your 7980 * application's target version is less than {@link Build.VERSION_CODES#P}, setting a 7981 * conversation title to a non-null value will make {@link #isGroupConversation()} return 7982 * {@code true} and passing {@code null} will make it return {@code false}. In 7983 * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} 7984 * to set group conversation status. 7985 * 7986 * @param conversationTitle Title displayed for this conversation 7987 * @return this object for method chaining 7988 */ setConversationTitle(@ullable CharSequence conversationTitle)7989 public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { 7990 mConversationTitle = conversationTitle; 7991 return this; 7992 } 7993 7994 /** 7995 * Return the title to be displayed on this conversation. May return {@code null}. 7996 */ 7997 @Nullable getConversationTitle()7998 public CharSequence getConversationTitle() { 7999 return mConversationTitle; 8000 } 8001 8002 /** 8003 * Sets the icon to be displayed on the conversation, derived from the shortcutId. 8004 * 8005 * @hide 8006 */ setShortcutIcon(@ullable Icon conversationIcon)8007 public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) { 8008 mShortcutIcon = conversationIcon; 8009 return this; 8010 } 8011 8012 /** 8013 * Return the icon to be displayed on this conversation, derived from the shortcutId. May 8014 * return {@code null}. 8015 * 8016 * @hide 8017 */ 8018 @Nullable getShortcutIcon()8019 public Icon getShortcutIcon() { 8020 return mShortcutIcon; 8021 } 8022 8023 /** 8024 * Sets the conversation type of this MessageStyle notification. 8025 * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R, 8026 * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and 8027 * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments. 8028 * 8029 * @hide 8030 */ setConversationType(@onversationType int conversationType)8031 public MessagingStyle setConversationType(@ConversationType int conversationType) { 8032 mConversationType = conversationType; 8033 return this; 8034 } 8035 8036 /** @hide */ 8037 @ConversationType getConversationType()8038 public int getConversationType() { 8039 return mConversationType; 8040 } 8041 8042 /** @hide */ getUnreadMessageCount()8043 public int getUnreadMessageCount() { 8044 return mUnreadMessageCount; 8045 } 8046 8047 /** @hide */ setUnreadMessageCount(int unreadMessageCount)8048 public MessagingStyle setUnreadMessageCount(int unreadMessageCount) { 8049 mUnreadMessageCount = unreadMessageCount; 8050 return this; 8051 } 8052 8053 /** 8054 * Adds a message for display by this notification. Convenience call for a simple 8055 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 8056 * @param text A {@link CharSequence} to be displayed as the message content 8057 * @param timestamp Time in milliseconds at which the message arrived 8058 * @param sender A {@link CharSequence} to be used for displaying the name of the 8059 * sender. Should be <code>null</code> for messages by the current user, in which case 8060 * the platform will insert {@link #getUserDisplayName()}. 8061 * Should be unique amongst all individuals in the conversation, and should be 8062 * consistent during re-posts of the notification. 8063 * 8064 * @see Message#Message(CharSequence, long, CharSequence) 8065 * 8066 * @return this object for method chaining 8067 * 8068 * @deprecated use {@link #addMessage(CharSequence, long, Person)} 8069 */ addMessage(CharSequence text, long timestamp, CharSequence sender)8070 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 8071 return addMessage(text, timestamp, 8072 sender == null ? null : new Person.Builder().setName(sender).build()); 8073 } 8074 8075 /** 8076 * Adds a message for display by this notification. Convenience call for a simple 8077 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 8078 * @param text A {@link CharSequence} to be displayed as the message content 8079 * @param timestamp Time in milliseconds at which the message arrived 8080 * @param sender The {@link Person} who sent the message. 8081 * Should be <code>null</code> for messages by the current user, in which case 8082 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8083 * 8084 * @see Message#Message(CharSequence, long, CharSequence) 8085 * 8086 * @return this object for method chaining 8087 */ addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)8088 public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, 8089 @Nullable Person sender) { 8090 return addMessage(new Message(text, timestamp, sender)); 8091 } 8092 8093 /** 8094 * Adds a {@link Message} for display in this notification. 8095 * 8096 * <p>The messages should be added in chronologic order, i.e. the oldest first, 8097 * the newest last. 8098 * 8099 * @param message The {@link Message} to be displayed 8100 * @return this object for method chaining 8101 */ addMessage(Message message)8102 public MessagingStyle addMessage(Message message) { 8103 mMessages.add(message); 8104 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 8105 mMessages.remove(0); 8106 } 8107 return this; 8108 } 8109 8110 /** 8111 * Adds a {@link Message} for historic context in this notification. 8112 * 8113 * <p>Messages should be added as historic if they are not the main subject of the 8114 * notification but may give context to a conversation. The system may choose to present 8115 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}. 8116 * 8117 * <p>The messages should be added in chronologic order, i.e. the oldest first, 8118 * the newest last. 8119 * 8120 * @param message The historic {@link Message} to be added 8121 * @return this object for method chaining 8122 */ addHistoricMessage(Message message)8123 public MessagingStyle addHistoricMessage(Message message) { 8124 mHistoricMessages.add(message); 8125 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 8126 mHistoricMessages.remove(0); 8127 } 8128 return this; 8129 } 8130 8131 /** 8132 * Gets the list of {@code Message} objects that represent the notification. 8133 */ getMessages()8134 public List<Message> getMessages() { 8135 return mMessages; 8136 } 8137 8138 /** 8139 * Gets the list of historic {@code Message}s in the notification. 8140 */ getHistoricMessages()8141 public List<Message> getHistoricMessages() { 8142 return mHistoricMessages; 8143 } 8144 8145 /** 8146 * Sets whether this conversation notification represents a group. If the app is targeting 8147 * Android P, this is required if the app wants to display the largeIcon set with 8148 * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden. 8149 * 8150 * @param isGroupConversation {@code true} if the conversation represents a group, 8151 * {@code false} otherwise. 8152 * @return this object for method chaining 8153 */ setGroupConversation(boolean isGroupConversation)8154 public MessagingStyle setGroupConversation(boolean isGroupConversation) { 8155 mIsGroupConversation = isGroupConversation; 8156 return this; 8157 } 8158 8159 /** 8160 * Returns {@code true} if this notification represents a group conversation, otherwise 8161 * {@code false}. 8162 * 8163 * <p> If the application that generated this {@link MessagingStyle} targets an SDK version 8164 * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or 8165 * not the conversation title is set; returning {@code true} if the conversation title is 8166 * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, 8167 * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for 8168 * named, non-group conversations. 8169 * 8170 * @see #setConversationTitle(CharSequence) 8171 */ isGroupConversation()8172 public boolean isGroupConversation() { 8173 // When target SDK version is < P, a non-null conversation title dictates if this is 8174 // as group conversation. 8175 if (mBuilder != null 8176 && mBuilder.mContext.getApplicationInfo().targetSdkVersion 8177 < Build.VERSION_CODES.P) { 8178 return mConversationTitle != null; 8179 } 8180 8181 return mIsGroupConversation; 8182 } 8183 8184 /** 8185 * @hide 8186 */ 8187 @Override addExtras(Bundle extras)8188 public void addExtras(Bundle extras) { 8189 super.addExtras(extras); 8190 if (mUser != null) { 8191 // For legacy usages 8192 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); 8193 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); 8194 } 8195 if (mConversationTitle != null) { 8196 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 8197 } 8198 if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES, 8199 Message.getBundleArrayForMessages(mMessages)); 8200 } 8201 if (!mHistoricMessages.isEmpty()) { extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, 8202 Message.getBundleArrayForMessages(mHistoricMessages)); 8203 } 8204 if (mShortcutIcon != null) { 8205 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon); 8206 } 8207 extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount); 8208 8209 fixTitleAndTextExtras(extras); 8210 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); 8211 } 8212 fixTitleAndTextExtras(Bundle extras)8213 private void fixTitleAndTextExtras(Bundle extras) { 8214 Message m = findLatestIncomingMessage(); 8215 CharSequence text = (m == null) ? null : m.mText; 8216 CharSequence sender = m == null ? null 8217 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 8218 ? mUser.getName() : m.mSender.getName(); 8219 CharSequence title; 8220 if (!TextUtils.isEmpty(mConversationTitle)) { 8221 if (!TextUtils.isEmpty(sender)) { 8222 BidiFormatter bidi = BidiFormatter.getInstance(); 8223 title = mBuilder.mContext.getString( 8224 com.android.internal.R.string.notification_messaging_title_template, 8225 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); 8226 } else { 8227 title = mConversationTitle; 8228 } 8229 } else { 8230 title = sender; 8231 } 8232 8233 if (title != null) { 8234 extras.putCharSequence(EXTRA_TITLE, title); 8235 } 8236 if (text != null) { 8237 extras.putCharSequence(EXTRA_TEXT, text); 8238 } 8239 } 8240 8241 /** 8242 * @hide 8243 */ 8244 @Override restoreFromExtras(Bundle extras)8245 protected void restoreFromExtras(Bundle extras) { 8246 super.restoreFromExtras(extras); 8247 8248 mUser = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); 8249 if (mUser == null) { 8250 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 8251 mUser = new Person.Builder().setName(displayName).build(); 8252 } 8253 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 8254 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES); 8255 mMessages = Message.getMessagesFromBundleArray(messages); 8256 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES); 8257 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); 8258 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); 8259 mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 8260 mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class); 8261 } 8262 8263 /** 8264 * @hide 8265 */ 8266 @Override makeContentView(boolean increasedHeight)8267 public RemoteViews makeContentView(boolean increasedHeight) { 8268 // All messaging templates contain the actions 8269 ArrayList<Action> originalActions = mBuilder.mActions; 8270 try { 8271 mBuilder.mActions = new ArrayList<>(); 8272 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL); 8273 } finally { 8274 mBuilder.mActions = originalActions; 8275 } 8276 } 8277 8278 /** 8279 * @hide 8280 * Spans are ignored when comparing text for visual difference. 8281 */ 8282 @Override areNotificationsVisiblyDifferent(Style other)8283 public boolean areNotificationsVisiblyDifferent(Style other) { 8284 if (other == null || getClass() != other.getClass()) { 8285 return true; 8286 } 8287 MessagingStyle newS = (MessagingStyle) other; 8288 List<MessagingStyle.Message> oldMs = getMessages(); 8289 List<MessagingStyle.Message> newMs = newS.getMessages(); 8290 8291 if (oldMs == null || newMs == null) { 8292 newMs = new ArrayList<>(); 8293 } 8294 8295 int n = oldMs.size(); 8296 if (n != newMs.size()) { 8297 return true; 8298 } 8299 for (int i = 0; i < n; i++) { 8300 MessagingStyle.Message oldM = oldMs.get(i); 8301 MessagingStyle.Message newM = newMs.get(i); 8302 if (!Objects.equals( 8303 String.valueOf(oldM.getText()), 8304 String.valueOf(newM.getText()))) { 8305 return true; 8306 } 8307 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { 8308 return true; 8309 } 8310 String oldSender = String.valueOf(oldM.getSenderPerson() == null 8311 ? oldM.getSender() 8312 : oldM.getSenderPerson().getName()); 8313 String newSender = String.valueOf(newM.getSenderPerson() == null 8314 ? newM.getSender() 8315 : newM.getSenderPerson().getName()); 8316 if (!Objects.equals(oldSender, newSender)) { 8317 return true; 8318 } 8319 8320 String oldKey = oldM.getSenderPerson() == null 8321 ? null : oldM.getSenderPerson().getKey(); 8322 String newKey = newM.getSenderPerson() == null 8323 ? null : newM.getSenderPerson().getKey(); 8324 if (!Objects.equals(oldKey, newKey)) { 8325 return true; 8326 } 8327 // Other fields (like timestamp) intentionally excluded 8328 } 8329 return false; 8330 } 8331 findLatestIncomingMessage()8332 private Message findLatestIncomingMessage() { 8333 return findLatestIncomingMessage(mMessages); 8334 } 8335 8336 /** 8337 * @hide 8338 */ 8339 @Nullable findLatestIncomingMessage( List<Message> messages)8340 public static Message findLatestIncomingMessage( 8341 List<Message> messages) { 8342 for (int i = messages.size() - 1; i >= 0; i--) { 8343 Message m = messages.get(i); 8344 // Incoming messages have a non-empty sender. 8345 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { 8346 return m; 8347 } 8348 } 8349 if (!messages.isEmpty()) { 8350 // No incoming messages, fall back to outgoing message 8351 return messages.get(messages.size() - 1); 8352 } 8353 return null; 8354 } 8355 8356 /** 8357 * @hide 8358 */ 8359 @Override makeBigContentView()8360 public RemoteViews makeBigContentView() { 8361 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG); 8362 } 8363 8364 /** 8365 * Create a messaging layout. 8366 * 8367 * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG, 8368 * VIEW_TYPE_HEADS_UP 8369 * @return the created remoteView. 8370 */ 8371 @NonNull makeMessagingView(int viewType)8372 private RemoteViews makeMessagingView(int viewType) { 8373 boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG; 8374 boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL; 8375 boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 8376 boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; 8377 boolean isHeaderless = !isConversationLayout && isCollapsed; 8378 8379 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 8380 ? super.mBigContentTitle 8381 : mConversationTitle; 8382 boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion 8383 >= Build.VERSION_CODES.P; 8384 boolean isOneToOne; 8385 CharSequence nameReplacement = null; 8386 if (!atLeastP) { 8387 isOneToOne = TextUtils.isEmpty(conversationTitle); 8388 if (hasOnlyWhiteSpaceSenders()) { 8389 isOneToOne = true; 8390 nameReplacement = conversationTitle; 8391 conversationTitle = null; 8392 } 8393 } else { 8394 isOneToOne = !isGroupConversation(); 8395 } 8396 if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) { 8397 conversationTitle = getOtherPersonName(); 8398 } 8399 8400 Icon largeIcon = mBuilder.mN.mLargeIcon; 8401 TemplateBindResult bindResult = new TemplateBindResult(); 8402 StandardTemplateParams p = mBuilder.mParams.reset() 8403 .viewType(viewType) 8404 .highlightExpander(isConversationLayout) 8405 .hideProgress(true) 8406 .title(isHeaderless ? conversationTitle : null) 8407 .text(null) 8408 .hideLeftIcon(isOneToOne) 8409 .hideRightIcon(hideRightIcons || isOneToOne) 8410 .headerTextSecondary(isHeaderless ? null : conversationTitle); 8411 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 8412 isConversationLayout 8413 ? mBuilder.getConversationLayoutResource() 8414 : isCollapsed 8415 ? mBuilder.getMessagingLayoutResource() 8416 : mBuilder.getBigMessagingLayoutResource(), 8417 p, 8418 bindResult); 8419 if (isConversationLayout) { 8420 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p); 8421 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 8422 } 8423 8424 addExtras(mBuilder.mN.extras); 8425 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 8426 mBuilder.getSmallIconColor(p)); 8427 contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", 8428 mBuilder.getPrimaryTextColor(p)); 8429 contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", 8430 mBuilder.getSecondaryTextColor(p)); 8431 contentView.setInt(R.id.status_bar_latest_event_content, 8432 "setNotificationBackgroundColor", 8433 mBuilder.getBackgroundColor(p)); 8434 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", 8435 isCollapsed); 8436 contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", 8437 mBuilder.mN.mLargeIcon); 8438 contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", 8439 nameReplacement); 8440 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", 8441 isOneToOne); 8442 contentView.setCharSequence(R.id.status_bar_latest_event_content, 8443 "setConversationTitle", conversationTitle); 8444 if (isConversationLayout) { 8445 contentView.setIcon(R.id.status_bar_latest_event_content, 8446 "setShortcutIcon", mShortcutIcon); 8447 contentView.setBoolean(R.id.status_bar_latest_event_content, 8448 "setIsImportantConversation", isImportantConversation); 8449 } 8450 if (isHeaderless) { 8451 // Collapsed legacy messaging style has a 1-line limit. 8452 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); 8453 } 8454 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 8455 largeIcon); 8456 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 8457 mBuilder.mN.extras); 8458 return contentView; 8459 } 8460 getKey(Person person)8461 private CharSequence getKey(Person person) { 8462 return person == null ? null 8463 : person.getKey() == null ? person.getName() : person.getKey(); 8464 } 8465 getOtherPersonName()8466 private CharSequence getOtherPersonName() { 8467 CharSequence userKey = getKey(mUser); 8468 for (int i = mMessages.size() - 1; i >= 0; i--) { 8469 Person sender = mMessages.get(i).getSenderPerson(); 8470 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) { 8471 return sender.getName(); 8472 } 8473 } 8474 return null; 8475 } 8476 hasOnlyWhiteSpaceSenders()8477 private boolean hasOnlyWhiteSpaceSenders() { 8478 for (int i = 0; i < mMessages.size(); i++) { 8479 Message m = mMessages.get(i); 8480 Person sender = m.getSenderPerson(); 8481 if (sender != null && !isWhiteSpace(sender.getName())) { 8482 return false; 8483 } 8484 } 8485 return true; 8486 } 8487 isWhiteSpace(CharSequence sender)8488 private boolean isWhiteSpace(CharSequence sender) { 8489 if (TextUtils.isEmpty(sender)) { 8490 return true; 8491 } 8492 if (sender.toString().matches("^\\s*$")) { 8493 return true; 8494 } 8495 // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround 8496 // For the presentation that we had. 8497 for (int i = 0; i < sender.length(); i++) { 8498 char c = sender.charAt(i); 8499 if (c != '\u200B') { 8500 return false; 8501 } 8502 } 8503 return true; 8504 } 8505 8506 /** 8507 * @hide 8508 */ 8509 @Override makeHeadsUpContentView(boolean increasedHeight)8510 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8511 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 8512 } 8513 8514 /** 8515 * @hide 8516 */ 8517 @Override reduceImageSizes(Context context)8518 public void reduceImageSizes(Context context) { 8519 super.reduceImageSizes(context); 8520 Resources resources = context.getResources(); 8521 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 8522 if (mShortcutIcon != null) { 8523 int maxSize = resources.getDimensionPixelSize( 8524 isLowRam ? R.dimen.notification_small_icon_size_low_ram 8525 : R.dimen.notification_small_icon_size); 8526 mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize); 8527 } 8528 8529 int maxAvatarSize = resources.getDimensionPixelSize( 8530 isLowRam ? R.dimen.notification_person_icon_max_size_low_ram 8531 : R.dimen.notification_person_icon_max_size); 8532 if (mUser != null && mUser.getIcon() != null) { 8533 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize); 8534 } 8535 8536 reduceMessagesIconSizes(mMessages, maxAvatarSize); 8537 reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize); 8538 } 8539 8540 /** 8541 * @hide 8542 */ reduceMessagesIconSizes(@ullable List<Message> messages, int maxSize)8543 private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) { 8544 if (messages == null) { 8545 return; 8546 } 8547 8548 for (Message message : messages) { 8549 Person sender = message.mSender; 8550 if (sender != null) { 8551 Icon icon = sender.getIcon(); 8552 if (icon != null) { 8553 icon.scaleDownIfNecessary(maxSize, maxSize); 8554 } 8555 } 8556 } 8557 } 8558 8559 public static final class Message { 8560 /** @hide */ 8561 public static final String KEY_TEXT = "text"; 8562 static final String KEY_TIMESTAMP = "time"; 8563 static final String KEY_SENDER = "sender"; 8564 static final String KEY_SENDER_PERSON = "sender_person"; 8565 static final String KEY_DATA_MIME_TYPE = "type"; 8566 static final String KEY_DATA_URI= "uri"; 8567 static final String KEY_EXTRAS_BUNDLE = "extras"; 8568 static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history"; 8569 8570 private final CharSequence mText; 8571 private final long mTimestamp; 8572 @Nullable 8573 private final Person mSender; 8574 /** True if this message was generated from the extra 8575 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} 8576 */ 8577 private final boolean mRemoteInputHistory; 8578 8579 private Bundle mExtras = new Bundle(); 8580 private String mDataMimeType; 8581 private Uri mDataUri; 8582 8583 /** 8584 * Constructor 8585 * @param text A {@link CharSequence} to be displayed as the message content 8586 * @param timestamp Time at which the message arrived 8587 * @param sender A {@link CharSequence} to be used for displaying the name of the 8588 * sender. Should be <code>null</code> for messages by the current user, in which case 8589 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 8590 * Should be unique amongst all individuals in the conversation, and should be 8591 * consistent during re-posts of the notification. 8592 * 8593 * @deprecated use {@code Message(CharSequence, long, Person)} 8594 */ Message(CharSequence text, long timestamp, CharSequence sender)8595 public Message(CharSequence text, long timestamp, CharSequence sender){ 8596 this(text, timestamp, sender == null ? null 8597 : new Person.Builder().setName(sender).build()); 8598 } 8599 8600 /** 8601 * Constructor 8602 * @param text A {@link CharSequence} to be displayed as the message content 8603 * @param timestamp Time at which the message arrived 8604 * @param sender The {@link Person} who sent the message. 8605 * Should be <code>null</code> for messages by the current user, in which case 8606 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8607 * <p> 8608 * The person provided should contain an Icon, set with 8609 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8610 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8611 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8612 * to differentiate between the different users. 8613 * </p> 8614 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)8615 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { 8616 this(text, timestamp, sender, false /* remoteHistory */); 8617 } 8618 8619 /** 8620 * Constructor 8621 * @param text A {@link CharSequence} to be displayed as the message content 8622 * @param timestamp Time at which the message arrived 8623 * @param sender The {@link Person} who sent the message. 8624 * Should be <code>null</code> for messages by the current user, in which case 8625 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8626 * @param remoteInputHistory True if the messages was generated from the extra 8627 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8628 * <p> 8629 * The person provided should contain an Icon, set with 8630 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 8631 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 8632 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 8633 * to differentiate between the different users. 8634 * </p> 8635 * @hide 8636 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)8637 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender, 8638 boolean remoteInputHistory) { 8639 mText = safeCharSequence(text); 8640 mTimestamp = timestamp; 8641 mSender = sender; 8642 mRemoteInputHistory = remoteInputHistory; 8643 } 8644 8645 /** 8646 * Sets a binary blob of data and an associated MIME type for a message. In the case 8647 * where the platform doesn't support the MIME type, the original text provided in the 8648 * constructor will be used. 8649 * @param dataMimeType The MIME type of the content. See 8650 * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME 8651 * types on Android and Android Wear. 8652 * @param dataUri The uri containing the content whose type is given by the MIME type. 8653 * <p class="note"> 8654 * <ol> 8655 * <li>Notification Listeners including the System UI need permission to access the 8656 * data the Uri points to. The recommended ways to do this are:</li> 8657 * <li>Store the data in your own ContentProvider, making sure that other apps have 8658 * the correct permission to access your provider. The preferred mechanism for 8659 * providing access is to use per-URI permissions which are temporary and only 8660 * grant access to the receiving application. An easy way to create a 8661 * ContentProvider like this is to use the FileProvider helper class.</li> 8662 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 8663 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 8664 * also store non-media types (see MediaStore.Files for more info). Files can be 8665 * inserted into the MediaStore using scanFile() after which a content:// style 8666 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 8667 * Note that once added to the system MediaStore the content is accessible to any 8668 * app on the device.</li> 8669 * </ol> 8670 * @return this object for method chaining 8671 */ setData(String dataMimeType, Uri dataUri)8672 public Message setData(String dataMimeType, Uri dataUri) { 8673 mDataMimeType = dataMimeType; 8674 mDataUri = dataUri; 8675 return this; 8676 } 8677 8678 /** 8679 * Get the text to be used for this message, or the fallback text if a type and content 8680 * Uri have been set 8681 */ getText()8682 public CharSequence getText() { 8683 return mText; 8684 } 8685 8686 /** 8687 * Get the time at which this message arrived 8688 */ getTimestamp()8689 public long getTimestamp() { 8690 return mTimestamp; 8691 } 8692 8693 /** 8694 * Get the extras Bundle for this message. 8695 */ getExtras()8696 public Bundle getExtras() { 8697 return mExtras; 8698 } 8699 8700 /** 8701 * Get the text used to display the contact's name in the messaging experience 8702 * 8703 * @deprecated use {@link #getSenderPerson()} 8704 */ getSender()8705 public CharSequence getSender() { 8706 return mSender == null ? null : mSender.getName(); 8707 } 8708 8709 /** 8710 * Get the sender associated with this message. 8711 */ 8712 @Nullable getSenderPerson()8713 public Person getSenderPerson() { 8714 return mSender; 8715 } 8716 8717 /** 8718 * Get the MIME type of the data pointed to by the Uri 8719 */ getDataMimeType()8720 public String getDataMimeType() { 8721 return mDataMimeType; 8722 } 8723 8724 /** 8725 * Get the Uri pointing to the content of the message. Can be null, in which case 8726 * {@see #getText()} is used. 8727 */ getDataUri()8728 public Uri getDataUri() { 8729 return mDataUri; 8730 } 8731 8732 /** 8733 * @return True if the message was generated from 8734 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 8735 * @hide 8736 */ isRemoteInputHistory()8737 public boolean isRemoteInputHistory() { 8738 return mRemoteInputHistory; 8739 } 8740 8741 /** 8742 * @hide 8743 */ 8744 @VisibleForTesting toBundle()8745 public Bundle toBundle() { 8746 Bundle bundle = new Bundle(); 8747 if (mText != null) { 8748 bundle.putCharSequence(KEY_TEXT, mText); 8749 } 8750 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 8751 if (mSender != null) { 8752 // Legacy listeners need this 8753 bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName())); 8754 bundle.putParcelable(KEY_SENDER_PERSON, mSender); 8755 } 8756 if (mDataMimeType != null) { 8757 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 8758 } 8759 if (mDataUri != null) { 8760 bundle.putParcelable(KEY_DATA_URI, mDataUri); 8761 } 8762 if (mExtras != null) { 8763 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 8764 } 8765 if (mRemoteInputHistory) { 8766 bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory); 8767 } 8768 return bundle; 8769 } 8770 getBundleArrayForMessages(List<Message> messages)8771 static Bundle[] getBundleArrayForMessages(List<Message> messages) { 8772 Bundle[] bundles = new Bundle[messages.size()]; 8773 final int N = messages.size(); 8774 for (int i = 0; i < N; i++) { 8775 bundles[i] = messages.get(i).toBundle(); 8776 } 8777 return bundles; 8778 } 8779 8780 /** 8781 * Returns a list of messages read from the given bundle list, e.g. 8782 * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}. 8783 */ 8784 @NonNull getMessagesFromBundleArray(@ullable Parcelable[] bundles)8785 public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) { 8786 if (bundles == null) { 8787 return new ArrayList<>(); 8788 } 8789 List<Message> messages = new ArrayList<>(bundles.length); 8790 for (int i = 0; i < bundles.length; i++) { 8791 if (bundles[i] instanceof Bundle) { 8792 Message message = getMessageFromBundle((Bundle)bundles[i]); 8793 if (message != null) { 8794 messages.add(message); 8795 } 8796 } 8797 } 8798 return messages; 8799 } 8800 8801 /** 8802 * Returns the message that is stored in the bundle (e.g. one of the values in the lists 8803 * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the 8804 * message couldn't be resolved. 8805 * @hide 8806 */ 8807 @Nullable getMessageFromBundle(@onNull Bundle bundle)8808 public static Message getMessageFromBundle(@NonNull Bundle bundle) { 8809 try { 8810 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 8811 return null; 8812 } else { 8813 8814 Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class); 8815 if (senderPerson == null) { 8816 // Legacy apps that use compat don't actually provide the sender objects 8817 // We need to fix the compat version to provide people / use 8818 // the native api instead 8819 CharSequence senderName = bundle.getCharSequence(KEY_SENDER); 8820 if (senderName != null) { 8821 senderPerson = new Person.Builder().setName(senderName).build(); 8822 } 8823 } 8824 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 8825 bundle.getLong(KEY_TIMESTAMP), 8826 senderPerson, 8827 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false)); 8828 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 8829 bundle.containsKey(KEY_DATA_URI)) { 8830 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 8831 bundle.getParcelable(KEY_DATA_URI, Uri.class)); 8832 } 8833 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 8834 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 8835 } 8836 return message; 8837 } 8838 } catch (ClassCastException e) { 8839 return null; 8840 } 8841 } 8842 } 8843 } 8844 8845 /** 8846 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 8847 * 8848 * Here's how you'd set the <code>InboxStyle</code> on a notification: 8849 * <pre class="prettyprint"> 8850 * Notification notif = new Notification.Builder(mContext) 8851 * .setContentTitle("5 New mails from " + sender.toString()) 8852 * .setContentText(subject) 8853 * .setSmallIcon(R.drawable.new_mail) 8854 * .setLargeIcon(aBitmap) 8855 * .setStyle(new Notification.InboxStyle() 8856 * .addLine(str1) 8857 * .addLine(str2) 8858 * .setContentTitle("") 8859 * .setSummaryText("+3 more")) 8860 * .build(); 8861 * </pre> 8862 * 8863 * @see Notification#bigContentView 8864 */ 8865 public static class InboxStyle extends Style { 8866 8867 /** 8868 * The number of lines of remote input history allowed until we start reducing lines. 8869 */ 8870 private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1; 8871 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 8872 InboxStyle()8873 public InboxStyle() { 8874 } 8875 8876 /** 8877 * @deprecated use {@code InboxStyle()}. 8878 */ 8879 @Deprecated InboxStyle(Builder builder)8880 public InboxStyle(Builder builder) { 8881 setBuilder(builder); 8882 } 8883 8884 /** 8885 * Overrides ContentTitle in the big form of the template. 8886 * This defaults to the value passed to setContentTitle(). 8887 */ setBigContentTitle(CharSequence title)8888 public InboxStyle setBigContentTitle(CharSequence title) { 8889 internalSetBigContentTitle(safeCharSequence(title)); 8890 return this; 8891 } 8892 8893 /** 8894 * Set the first line of text after the detail section in the big form of the template. 8895 */ setSummaryText(CharSequence cs)8896 public InboxStyle setSummaryText(CharSequence cs) { 8897 internalSetSummaryText(safeCharSequence(cs)); 8898 return this; 8899 } 8900 8901 /** 8902 * Append a line to the digest section of the Inbox notification. 8903 */ addLine(CharSequence cs)8904 public InboxStyle addLine(CharSequence cs) { 8905 mTexts.add(safeCharSequence(cs)); 8906 return this; 8907 } 8908 8909 /** 8910 * @hide 8911 */ getLines()8912 public ArrayList<CharSequence> getLines() { 8913 return mTexts; 8914 } 8915 8916 /** 8917 * @hide 8918 */ addExtras(Bundle extras)8919 public void addExtras(Bundle extras) { 8920 super.addExtras(extras); 8921 8922 CharSequence[] a = new CharSequence[mTexts.size()]; 8923 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 8924 } 8925 8926 /** 8927 * @hide 8928 */ 8929 @Override restoreFromExtras(Bundle extras)8930 protected void restoreFromExtras(Bundle extras) { 8931 super.restoreFromExtras(extras); 8932 8933 mTexts.clear(); 8934 if (extras.containsKey(EXTRA_TEXT_LINES)) { 8935 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 8936 } 8937 } 8938 8939 /** 8940 * @hide 8941 */ makeBigContentView()8942 public RemoteViews makeBigContentView() { 8943 StandardTemplateParams p = mBuilder.mParams.reset() 8944 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 8945 .fillTextsFrom(mBuilder).text(null); 8946 TemplateBindResult result = new TemplateBindResult(); 8947 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result); 8948 8949 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 8950 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 8951 8952 // Make sure all rows are gone in case we reuse a view. 8953 for (int rowId : rowIds) { 8954 contentView.setViewVisibility(rowId, View.GONE); 8955 } 8956 8957 int i=0; 8958 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 8959 R.dimen.notification_inbox_item_top_padding); 8960 boolean first = true; 8961 int onlyViewId = 0; 8962 int maxRows = rowIds.length; 8963 if (mBuilder.mActions.size() > 0) { 8964 maxRows--; 8965 } 8966 RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( 8967 mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 8968 RemoteInputHistoryItem.class); 8969 if (remoteInputHistory != null 8970 && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { 8971 // Let's remove some messages to make room for the remote input history. 8972 // 1 is always able to fit, but let's remove them if they are 2 or 3 8973 int numRemoteInputs = Math.min(remoteInputHistory.length, 8974 MAX_REMOTE_INPUT_HISTORY_LINES); 8975 int totalNumRows = mTexts.size() + numRemoteInputs 8976 - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION; 8977 if (totalNumRows > maxRows) { 8978 int overflow = totalNumRows - maxRows; 8979 if (mTexts.size() > maxRows) { 8980 // Heuristic: if the Texts don't fit anyway, we'll rather drop the last 8981 // few messages, even with the remote input 8982 maxRows -= overflow; 8983 } else { 8984 // otherwise we drop the first messages 8985 i = overflow; 8986 } 8987 } 8988 } 8989 while (i < mTexts.size() && i < maxRows) { 8990 CharSequence str = mTexts.get(i); 8991 if (!TextUtils.isEmpty(str)) { 8992 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 8993 contentView.setTextViewText(rowIds[i], 8994 mBuilder.processTextSpans(mBuilder.processLegacyText(str))); 8995 mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); 8996 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 8997 if (first) { 8998 onlyViewId = rowIds[i]; 8999 } else { 9000 onlyViewId = 0; 9001 } 9002 first = false; 9003 } 9004 i++; 9005 } 9006 if (onlyViewId != 0) { 9007 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 9008 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 9009 R.dimen.notification_text_margin_top); 9010 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 9011 } 9012 9013 return contentView; 9014 } 9015 9016 /** 9017 * @hide 9018 */ 9019 @Override areNotificationsVisiblyDifferent(Style other)9020 public boolean areNotificationsVisiblyDifferent(Style other) { 9021 if (other == null || getClass() != other.getClass()) { 9022 return true; 9023 } 9024 InboxStyle newS = (InboxStyle) other; 9025 9026 final ArrayList<CharSequence> myLines = getLines(); 9027 final ArrayList<CharSequence> newLines = newS.getLines(); 9028 final int n = myLines.size(); 9029 if (n != newLines.size()) { 9030 return true; 9031 } 9032 9033 for (int i = 0; i < n; i++) { 9034 if (!Objects.equals( 9035 String.valueOf(myLines.get(i)), 9036 String.valueOf(newLines.get(i)))) { 9037 return true; 9038 } 9039 } 9040 return false; 9041 } 9042 } 9043 9044 /** 9045 * Notification style for media playback notifications. 9046 * 9047 * In the expanded form, {@link Notification#bigContentView}, up to 5 9048 * {@link Notification.Action}s specified with 9049 * {@link Notification.Builder#addAction(Action) addAction} will be 9050 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 9051 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 9052 * treated as album artwork. 9053 * <p> 9054 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 9055 * {@link Notification#contentView}; by providing action indices to 9056 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 9057 * in the standard view alongside the usual content. 9058 * <p> 9059 * Notifications created with MediaStyle will have their category set to 9060 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 9061 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 9062 * <p> 9063 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 9064 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 9065 * the System UI can identify this as a notification representing an active media session 9066 * and respond accordingly (by showing album artwork in the lockscreen, for example). 9067 * 9068 * <p> 9069 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a 9070 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized. 9071 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}. 9072 * <p> 9073 * 9074 * To use this style with your Notification, feed it to 9075 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9076 * <pre class="prettyprint"> 9077 * Notification noti = new Notification.Builder() 9078 * .setSmallIcon(R.drawable.ic_stat_player) 9079 * .setContentTitle("Track title") 9080 * .setContentText("Artist - Album") 9081 * .setLargeIcon(albumArtBitmap)) 9082 * .setStyle(<b>new Notification.MediaStyle()</b> 9083 * .setMediaSession(mySession)) 9084 * .build(); 9085 * </pre> 9086 * 9087 * @see Notification#bigContentView 9088 * @see Notification.Builder#setColorized(boolean) 9089 */ 9090 public static class MediaStyle extends Style { 9091 // Changing max media buttons requires also changing templates 9092 // (notification_template_material_media and notification_template_material_big_media). 9093 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 9094 static final int MAX_MEDIA_BUTTONS = 5; 9095 @IdRes private static final int[] MEDIA_BUTTON_IDS = { 9096 R.id.action0, 9097 R.id.action1, 9098 R.id.action2, 9099 R.id.action3, 9100 R.id.action4, 9101 }; 9102 9103 private int[] mActionsToShowInCompact = null; 9104 private MediaSession.Token mToken; 9105 private CharSequence mDeviceName; 9106 private int mDeviceIcon; 9107 private PendingIntent mDeviceIntent; 9108 MediaStyle()9109 public MediaStyle() { 9110 } 9111 9112 /** 9113 * @deprecated use {@code MediaStyle()}. 9114 */ 9115 @Deprecated MediaStyle(Builder builder)9116 public MediaStyle(Builder builder) { 9117 setBuilder(builder); 9118 } 9119 9120 /** 9121 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 9122 * notification view. 9123 * 9124 * @param actions the indices of the actions to show in the compact notification view 9125 */ setShowActionsInCompactView(int...actions)9126 public MediaStyle setShowActionsInCompactView(int...actions) { 9127 mActionsToShowInCompact = actions; 9128 return this; 9129 } 9130 9131 /** 9132 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 9133 * to provide additional playback information and control to the SystemUI. 9134 */ setMediaSession(MediaSession.Token token)9135 public MediaStyle setMediaSession(MediaSession.Token token) { 9136 mToken = token; 9137 return this; 9138 } 9139 9140 /** 9141 * For media notifications associated with playback on a remote device, provide device 9142 * information that will replace the default values for the output switcher chip on the 9143 * media control, as well as an intent to use when the output switcher chip is tapped, 9144 * on devices where this is supported. 9145 * <p> 9146 * This method is intended for system applications to provide information and/or 9147 * functionality that would otherwise be unavailable to the default output switcher because 9148 * the media originated on a remote device. 9149 * 9150 * @param deviceName The name of the remote device to display 9151 * @param iconResource Icon resource representing the device 9152 * @param chipIntent PendingIntent to send when the output switcher is tapped. May be 9153 * {@code null}, in which case the output switcher will be disabled. 9154 * This intent should open an Activity or it will be ignored. 9155 * @return MediaStyle 9156 * 9157 * @hide 9158 */ 9159 @SystemApi 9160 @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) 9161 @NonNull setRemotePlaybackInfo(@onNull CharSequence deviceName, @DrawableRes int iconResource, @Nullable PendingIntent chipIntent)9162 public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName, 9163 @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) { 9164 mDeviceName = deviceName; 9165 mDeviceIcon = iconResource; 9166 mDeviceIntent = chipIntent; 9167 return this; 9168 } 9169 9170 /** 9171 * @hide 9172 */ 9173 @Override 9174 @UnsupportedAppUsage buildStyled(Notification wip)9175 public Notification buildStyled(Notification wip) { 9176 super.buildStyled(wip); 9177 if (wip.category == null) { 9178 wip.category = Notification.CATEGORY_TRANSPORT; 9179 } 9180 return wip; 9181 } 9182 9183 /** 9184 * @hide 9185 */ 9186 @Override makeContentView(boolean increasedHeight)9187 public RemoteViews makeContentView(boolean increasedHeight) { 9188 return makeMediaContentView(null /* customContent */); 9189 } 9190 9191 /** 9192 * @hide 9193 */ 9194 @Override makeBigContentView()9195 public RemoteViews makeBigContentView() { 9196 return makeMediaBigContentView(null /* customContent */); 9197 } 9198 9199 /** 9200 * @hide 9201 */ 9202 @Override makeHeadsUpContentView(boolean increasedHeight)9203 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9204 return makeMediaContentView(null /* customContent */); 9205 } 9206 9207 /** @hide */ 9208 @Override addExtras(Bundle extras)9209 public void addExtras(Bundle extras) { 9210 super.addExtras(extras); 9211 9212 if (mToken != null) { 9213 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 9214 } 9215 if (mActionsToShowInCompact != null) { 9216 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 9217 } 9218 if (mDeviceName != null) { 9219 extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName); 9220 } 9221 if (mDeviceIcon > 0) { 9222 extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon); 9223 } 9224 if (mDeviceIntent != null) { 9225 extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent); 9226 } 9227 } 9228 9229 /** 9230 * @hide 9231 */ 9232 @Override restoreFromExtras(Bundle extras)9233 protected void restoreFromExtras(Bundle extras) { 9234 super.restoreFromExtras(extras); 9235 9236 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 9237 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class); 9238 } 9239 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 9240 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 9241 } 9242 if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) { 9243 mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE); 9244 } 9245 if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) { 9246 mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON); 9247 } 9248 if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) { 9249 mDeviceIntent = extras.getParcelable( 9250 EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class); 9251 } 9252 } 9253 9254 /** 9255 * @hide 9256 */ 9257 @Override areNotificationsVisiblyDifferent(Style other)9258 public boolean areNotificationsVisiblyDifferent(Style other) { 9259 if (other == null || getClass() != other.getClass()) { 9260 return true; 9261 } 9262 // All fields to compare are on the Notification object 9263 return false; 9264 } 9265 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)9266 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 9267 Action action, StandardTemplateParams p) { 9268 final boolean tombstone = (action.actionIntent == null); 9269 container.setViewVisibility(buttonId, View.VISIBLE); 9270 container.setImageViewIcon(buttonId, action.getIcon()); 9271 9272 // If the action buttons should not be tinted, then just use the default 9273 // notification color. Otherwise, just use the passed-in color. 9274 int tintColor = mBuilder.getStandardActionColor(p); 9275 9276 container.setDrawableTint(buttonId, false, tintColor, 9277 PorterDuff.Mode.SRC_ATOP); 9278 9279 int rippleAlpha = mBuilder.getColors(p).getRippleAlpha(); 9280 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 9281 Color.blue(tintColor)); 9282 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 9283 9284 if (!tombstone) { 9285 container.setOnClickPendingIntent(buttonId, action.actionIntent); 9286 } 9287 container.setContentDescription(buttonId, action.title); 9288 } 9289 9290 /** @hide */ makeMediaContentView(@ullable RemoteViews customContent)9291 protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) { 9292 final int numActions = mBuilder.mActions.size(); 9293 final int numActionsToShow = Math.min(mActionsToShowInCompact == null 9294 ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 9295 if (numActionsToShow > numActions) { 9296 throw new IllegalArgumentException(String.format( 9297 "setShowActionsInCompactView: action %d out of bounds (max %d)", 9298 numActions, numActions - 1)); 9299 } 9300 9301 StandardTemplateParams p = mBuilder.mParams.reset() 9302 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 9303 .hideTime(numActionsToShow > 1) // hide if actions wider than a right icon 9304 .hideSubText(numActionsToShow > 1) // hide if actions wider than a right icon 9305 .hideLeftIcon(false) // allow large icon on left when grouped 9306 .hideRightIcon(numActionsToShow > 0) // right icon or actions; not both 9307 .hideProgress(true) 9308 .fillTextsFrom(mBuilder); 9309 TemplateBindResult result = new TemplateBindResult(); 9310 RemoteViews template = mBuilder.applyStandardTemplate( 9311 R.layout.notification_template_material_media, p, 9312 null /* result */); 9313 9314 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 9315 if (i < numActionsToShow) { 9316 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 9317 bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p); 9318 } else { 9319 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9320 } 9321 } 9322 // Prevent a swooping expand animation when there are no actions 9323 boolean hasActions = numActionsToShow != 0; 9324 template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE); 9325 9326 // Add custom view if provided by subclass. 9327 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9328 return template; 9329 } 9330 9331 /** @hide */ makeMediaBigContentView(@ullable RemoteViews customContent)9332 protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) { 9333 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 9334 StandardTemplateParams p = mBuilder.mParams.reset() 9335 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9336 .hideProgress(true) 9337 .fillTextsFrom(mBuilder); 9338 TemplateBindResult result = new TemplateBindResult(); 9339 RemoteViews template = mBuilder.applyStandardTemplate( 9340 R.layout.notification_template_material_big_media, p , result); 9341 9342 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 9343 if (i < actionCount) { 9344 bindMediaActionButton(template, 9345 MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 9346 } else { 9347 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 9348 } 9349 } 9350 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 9351 return template; 9352 } 9353 } 9354 9355 /** 9356 * Helper class for generating large-format notifications that include a large image attachment. 9357 * 9358 * Here's how you'd set the <code>CallStyle</code> on a notification: 9359 * <pre class="prettyprint"> 9360 * Notification notif = new Notification.Builder(mContext) 9361 * .setSmallIcon(R.drawable.new_post) 9362 * .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent)) 9363 * .build(); 9364 * </pre> 9365 */ 9366 public static class CallStyle extends Style { 9367 /** @hide */ 9368 public static final int CALL_TYPE_INCOMING = 1; 9369 /** @hide */ 9370 public static final int CALL_TYPE_ONGOING = 2; 9371 /** @hide */ 9372 public static final int CALL_TYPE_SCREENING = 3; 9373 9374 /** 9375 * This is a key used privately on the action.extras to give spacing priority 9376 * to the required call actions 9377 */ 9378 private static final String KEY_ACTION_PRIORITY = "key_action_priority"; 9379 9380 private int mCallType; 9381 private Person mPerson; 9382 private PendingIntent mAnswerIntent; 9383 private PendingIntent mDeclineIntent; 9384 private PendingIntent mHangUpIntent; 9385 private boolean mIsVideo; 9386 private Integer mAnswerButtonColor; 9387 private Integer mDeclineButtonColor; 9388 private Icon mVerificationIcon; 9389 private CharSequence mVerificationText; 9390 CallStyle()9391 CallStyle() { 9392 } 9393 9394 /** 9395 * Create a CallStyle for an incoming call. 9396 * This notification will have a decline and an answer action, will allow a single 9397 * custom {@link Builder#addAction(Action) action}, and will have a default 9398 * {@link Builder#setContentText(CharSequence) content text} for an incoming call. 9399 * 9400 * @param person The person displayed as the caller. 9401 * The person also needs to have a non-empty name associated with it. 9402 * @param declineIntent The intent to be sent when the user taps the decline action 9403 * @param answerIntent The intent to be sent when the user taps the answer action 9404 */ 9405 @NonNull forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)9406 public static CallStyle forIncomingCall(@NonNull Person person, 9407 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) { 9408 return new CallStyle(CALL_TYPE_INCOMING, person, 9409 null /* hangUpIntent */, 9410 requireNonNull(declineIntent, "declineIntent is required"), 9411 requireNonNull(answerIntent, "answerIntent is required") 9412 ); 9413 } 9414 9415 /** 9416 * Create a CallStyle for an ongoing call. 9417 * This notification will have a hang up action, will allow up to two 9418 * custom {@link Builder#addAction(Action) actions}, and will have a default 9419 * {@link Builder#setContentText(CharSequence) content text} for an ongoing call. 9420 * 9421 * @param person The person displayed as being on the other end of the call. 9422 * The person also needs to have a non-empty name associated with it. 9423 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9424 */ 9425 @NonNull forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)9426 public static CallStyle forOngoingCall(@NonNull Person person, 9427 @NonNull PendingIntent hangUpIntent) { 9428 return new CallStyle(CALL_TYPE_ONGOING, person, 9429 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9430 null /* declineIntent */, 9431 null /* answerIntent */ 9432 ); 9433 } 9434 9435 /** 9436 * Create a CallStyle for a call that is being screened. 9437 * This notification will have a hang up and an answer action, will allow a single 9438 * custom {@link Builder#addAction(Action) action}, and will have a default 9439 * {@link Builder#setContentText(CharSequence) content text} for a call that is being 9440 * screened. 9441 * 9442 * @param person The person displayed as the caller. 9443 * The person also needs to have a non-empty name associated with it. 9444 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9445 * @param answerIntent The intent to be sent when the user taps the answer action 9446 */ 9447 @NonNull forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)9448 public static CallStyle forScreeningCall(@NonNull Person person, 9449 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) { 9450 return new CallStyle(CALL_TYPE_SCREENING, person, 9451 requireNonNull(hangUpIntent, "hangUpIntent is required"), 9452 null /* declineIntent */, 9453 requireNonNull(answerIntent, "answerIntent is required") 9454 ); 9455 } 9456 9457 /** 9458 * @param person The person displayed for the incoming call. 9459 * The user also needs to have a non-empty name associated with it. 9460 * @param hangUpIntent The intent to be sent when the user taps the hang up action 9461 * @param declineIntent The intent to be sent when the user taps the decline action 9462 * @param answerIntent The intent to be sent when the user taps the answer action 9463 */ CallStyle(int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)9464 private CallStyle(int callType, @NonNull Person person, 9465 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, 9466 @Nullable PendingIntent answerIntent) { 9467 if (person == null || TextUtils.isEmpty(person.getName())) { 9468 throw new IllegalArgumentException("person must have a non-empty a name"); 9469 } 9470 mCallType = callType; 9471 mPerson = person; 9472 mAnswerIntent = answerIntent; 9473 mDeclineIntent = declineIntent; 9474 mHangUpIntent = hangUpIntent; 9475 } 9476 9477 /** 9478 * Sets whether the call is a video call, which may affect the icons or text used on the 9479 * required action buttons. 9480 */ 9481 @NonNull setIsVideo(boolean isVideo)9482 public CallStyle setIsVideo(boolean isVideo) { 9483 mIsVideo = isVideo; 9484 return this; 9485 } 9486 9487 /** 9488 * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text} 9489 * as a verification status of the caller. 9490 */ 9491 @NonNull setVerificationIcon(@ullable Icon verificationIcon)9492 public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) { 9493 mVerificationIcon = verificationIcon; 9494 return this; 9495 } 9496 9497 /** 9498 * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon} 9499 * as a verification status of the caller. 9500 */ 9501 @NonNull setVerificationText(@ullable CharSequence verificationText)9502 public CallStyle setVerificationText(@Nullable CharSequence verificationText) { 9503 mVerificationText = safeCharSequence(verificationText); 9504 return this; 9505 } 9506 9507 /** 9508 * Optional color to be used as a hint for the Answer action button's color. 9509 * The system may change this color to ensure sufficient contrast with the background. 9510 * The system may choose to disregard this hint if the notification is not colorized. 9511 */ 9512 @NonNull setAnswerButtonColorHint(@olorInt int color)9513 public CallStyle setAnswerButtonColorHint(@ColorInt int color) { 9514 mAnswerButtonColor = color; 9515 return this; 9516 } 9517 9518 /** 9519 * Optional color to be used as a hint for the Decline or Hang Up action button's color. 9520 * The system may change this color to ensure sufficient contrast with the background. 9521 * The system may choose to disregard this hint if the notification is not colorized. 9522 */ 9523 @NonNull setDeclineButtonColorHint(@olorInt int color)9524 public CallStyle setDeclineButtonColorHint(@ColorInt int color) { 9525 mDeclineButtonColor = color; 9526 return this; 9527 } 9528 9529 /** @hide */ 9530 @Override buildStyled(Notification wip)9531 public Notification buildStyled(Notification wip) { 9532 wip = super.buildStyled(wip); 9533 // ensure that the actions in the builder and notification are corrected. 9534 mBuilder.mActions = getActionsListWithSystemActions(); 9535 wip.actions = new Action[mBuilder.mActions.size()]; 9536 mBuilder.mActions.toArray(wip.actions); 9537 return wip; 9538 } 9539 9540 /** 9541 * @hide 9542 */ displayCustomViewInline()9543 public boolean displayCustomViewInline() { 9544 // This is a lie; True is returned to make sure that the custom view is not used 9545 // instead of the template, but it will not actually be included. 9546 return true; 9547 } 9548 9549 /** 9550 * @hide 9551 */ 9552 @Override purgeResources()9553 public void purgeResources() { 9554 super.purgeResources(); 9555 if (mVerificationIcon != null) { 9556 mVerificationIcon.convertToAshmem(); 9557 } 9558 } 9559 9560 /** 9561 * @hide 9562 */ 9563 @Override reduceImageSizes(Context context)9564 public void reduceImageSizes(Context context) { 9565 super.reduceImageSizes(context); 9566 if (mVerificationIcon != null) { 9567 int rightIconSize = context.getResources().getDimensionPixelSize( 9568 ActivityManager.isLowRamDeviceStatic() 9569 ? R.dimen.notification_right_icon_size_low_ram 9570 : R.dimen.notification_right_icon_size); 9571 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 9572 } 9573 } 9574 9575 /** 9576 * @hide 9577 */ 9578 @Override makeContentView(boolean increasedHeight)9579 public RemoteViews makeContentView(boolean increasedHeight) { 9580 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL); 9581 } 9582 9583 /** 9584 * @hide 9585 */ 9586 @Override makeHeadsUpContentView(boolean increasedHeight)9587 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9588 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 9589 } 9590 9591 /** 9592 * @hide 9593 */ makeBigContentView()9594 public RemoteViews makeBigContentView() { 9595 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG); 9596 } 9597 9598 @NonNull makeNegativeAction()9599 private Action makeNegativeAction() { 9600 if (mDeclineIntent == null) { 9601 return makeAction(R.drawable.ic_call_decline, 9602 R.string.call_notification_hang_up_action, 9603 mDeclineButtonColor, R.color.call_notification_decline_color, 9604 mHangUpIntent); 9605 } else { 9606 return makeAction(R.drawable.ic_call_decline, 9607 R.string.call_notification_decline_action, 9608 mDeclineButtonColor, R.color.call_notification_decline_color, 9609 mDeclineIntent); 9610 } 9611 } 9612 9613 @Nullable makeAnswerAction()9614 private Action makeAnswerAction() { 9615 return mAnswerIntent == null ? null : makeAction( 9616 mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer, 9617 mIsVideo ? R.string.call_notification_answer_video_action 9618 : R.string.call_notification_answer_action, 9619 mAnswerButtonColor, R.color.call_notification_answer_color, 9620 mAnswerIntent); 9621 } 9622 9623 @NonNull makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)9624 private Action makeAction(@DrawableRes int icon, @StringRes int title, 9625 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) { 9626 if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) { 9627 colorInt = mBuilder.mContext.getColor(defaultColorRes); 9628 } 9629 Action action = new Action.Builder(Icon.createWithResource("", icon), 9630 new SpannableStringBuilder().append(mBuilder.mContext.getString(title), 9631 new ForegroundColorSpan(colorInt), 9632 SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE), 9633 intent).build(); 9634 action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true); 9635 return action; 9636 } 9637 isActionAddedByCallStyle(Action action)9638 private boolean isActionAddedByCallStyle(Action action) { 9639 // This is an internal extra added by the style to these actions. If an app were to add 9640 // this extra to the action themselves, the action would be dropped. :shrug: 9641 return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY); 9642 } 9643 9644 /** 9645 * Gets the actions list for the call with the answer/decline/hangUp actions inserted in 9646 * the correct place. This returns the correct result even if the system actions have 9647 * already been added, and even if more actions were added since then. 9648 * @hide 9649 */ 9650 @NonNull getActionsListWithSystemActions()9651 public ArrayList<Action> getActionsListWithSystemActions() { 9652 // Define the system actions we expect to see 9653 final Action firstAction = makeNegativeAction(); 9654 final Action lastAction = makeAnswerAction(); 9655 9656 // Start creating the result list. 9657 int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS; 9658 ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS); 9659 9660 // Always have a first action. 9661 resultActions.add(firstAction); 9662 --nonContextualActionSlotsRemaining; 9663 9664 // Copy actions into the new list, correcting system actions. 9665 if (mBuilder.mActions != null) { 9666 for (Notification.Action action : mBuilder.mActions) { 9667 if (action.isContextual()) { 9668 // Always include all contextual actions 9669 resultActions.add(action); 9670 } else if (isActionAddedByCallStyle(action)) { 9671 // Drop any old versions of system actions 9672 } else { 9673 // Copy non-contextual actions; decrement the remaining action slots. 9674 resultActions.add(action); 9675 --nonContextualActionSlotsRemaining; 9676 } 9677 // If there's exactly one action slot left, fill it with the lastAction. 9678 if (lastAction != null && nonContextualActionSlotsRemaining == 1) { 9679 resultActions.add(lastAction); 9680 --nonContextualActionSlotsRemaining; 9681 } 9682 } 9683 } 9684 // If there are any action slots left, the lastAction still needs to be added. 9685 if (lastAction != null && nonContextualActionSlotsRemaining >= 1) { 9686 resultActions.add(lastAction); 9687 } 9688 return resultActions; 9689 } 9690 makeCallLayout(int viewType)9691 private RemoteViews makeCallLayout(int viewType) { 9692 final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL; 9693 Bundle extras = mBuilder.mN.extras; 9694 CharSequence title = mPerson != null ? mPerson.getName() : null; 9695 CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 9696 if (text == null) { 9697 text = getDefaultText(); 9698 } 9699 9700 // Bind standard template 9701 StandardTemplateParams p = mBuilder.mParams.reset() 9702 .viewType(viewType) 9703 .callStyleActions(true) 9704 .allowTextWithProgress(true) 9705 .hideLeftIcon(true) 9706 .hideRightIcon(true) 9707 .hideAppName(isCollapsed) 9708 .titleViewId(R.id.conversation_text) 9709 .title(title) 9710 .text(text) 9711 .summaryText(mBuilder.processLegacyText(mVerificationText)); 9712 mBuilder.mActions = getActionsListWithSystemActions(); 9713 final RemoteViews contentView; 9714 if (isCollapsed) { 9715 contentView = mBuilder.applyStandardTemplate( 9716 R.layout.notification_template_material_call, p, null /* result */); 9717 } else { 9718 contentView = mBuilder.applyStandardTemplateWithActions( 9719 R.layout.notification_template_material_big_call, p, null /* result */); 9720 } 9721 9722 // Bind some extra conversation-specific header fields. 9723 if (!p.mHideAppName) { 9724 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 9725 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE); 9726 } 9727 bindCallerVerification(contentView, p); 9728 9729 // Bind some custom CallLayout properties 9730 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 9731 mBuilder.getSmallIconColor(p)); 9732 contentView.setInt(R.id.status_bar_latest_event_content, 9733 "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); 9734 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 9735 mBuilder.mN.mLargeIcon); 9736 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 9737 mBuilder.mN.extras); 9738 9739 return contentView; 9740 } 9741 bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)9742 private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) { 9743 String iconContentDescription = null; 9744 boolean showDivider = true; 9745 if (mVerificationIcon != null) { 9746 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon); 9747 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */, 9748 mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 9749 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE); 9750 iconContentDescription = mBuilder.mContext.getString( 9751 R.string.notification_verified_content_description); 9752 showDivider = false; // the icon replaces the divider 9753 } else { 9754 contentView.setViewVisibility(R.id.verification_icon, View.GONE); 9755 } 9756 if (!TextUtils.isEmpty(mVerificationText)) { 9757 contentView.setTextViewText(R.id.verification_text, mVerificationText); 9758 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p); 9759 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE); 9760 iconContentDescription = null; // let the app's text take precedence 9761 } else { 9762 contentView.setViewVisibility(R.id.verification_text, View.GONE); 9763 showDivider = false; // no divider if no text 9764 } 9765 contentView.setContentDescription(R.id.verification_icon, iconContentDescription); 9766 if (showDivider) { 9767 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE); 9768 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p); 9769 } else { 9770 contentView.setViewVisibility(R.id.verification_divider, View.GONE); 9771 } 9772 } 9773 9774 @Nullable getDefaultText()9775 private String getDefaultText() { 9776 switch (mCallType) { 9777 case CALL_TYPE_INCOMING: 9778 return mBuilder.mContext.getString(R.string.call_notification_incoming_text); 9779 case CALL_TYPE_ONGOING: 9780 return mBuilder.mContext.getString(R.string.call_notification_ongoing_text); 9781 case CALL_TYPE_SCREENING: 9782 return mBuilder.mContext.getString(R.string.call_notification_screening_text); 9783 } 9784 return null; 9785 } 9786 9787 /** 9788 * @hide 9789 */ addExtras(Bundle extras)9790 public void addExtras(Bundle extras) { 9791 super.addExtras(extras); 9792 extras.putInt(EXTRA_CALL_TYPE, mCallType); 9793 extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo); 9794 extras.putParcelable(EXTRA_CALL_PERSON, mPerson); 9795 if (mVerificationIcon != null) { 9796 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon); 9797 } 9798 if (mVerificationText != null) { 9799 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText); 9800 } 9801 if (mAnswerIntent != null) { 9802 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent); 9803 } 9804 if (mDeclineIntent != null) { 9805 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent); 9806 } 9807 if (mHangUpIntent != null) { 9808 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent); 9809 } 9810 if (mAnswerButtonColor != null) { 9811 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor); 9812 } 9813 if (mDeclineButtonColor != null) { 9814 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor); 9815 } 9816 fixTitleAndTextExtras(extras); 9817 } 9818 fixTitleAndTextExtras(Bundle extras)9819 private void fixTitleAndTextExtras(Bundle extras) { 9820 CharSequence sender = mPerson != null ? mPerson.getName() : null; 9821 if (sender != null) { 9822 extras.putCharSequence(EXTRA_TITLE, sender); 9823 } 9824 if (extras.getCharSequence(EXTRA_TEXT) == null) { 9825 extras.putCharSequence(EXTRA_TEXT, getDefaultText()); 9826 } 9827 } 9828 9829 /** 9830 * @hide 9831 */ 9832 @Override restoreFromExtras(Bundle extras)9833 protected void restoreFromExtras(Bundle extras) { 9834 super.restoreFromExtras(extras); 9835 mCallType = extras.getInt(EXTRA_CALL_TYPE); 9836 mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO); 9837 mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); 9838 mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON); 9839 mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT); 9840 mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class); 9841 mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class); 9842 mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class); 9843 mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR) 9844 ? extras.getInt(EXTRA_ANSWER_COLOR) : null; 9845 mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR) 9846 ? extras.getInt(EXTRA_DECLINE_COLOR) : null; 9847 } 9848 9849 /** 9850 * @hide 9851 */ 9852 @Override hasSummaryInHeader()9853 public boolean hasSummaryInHeader() { 9854 return false; 9855 } 9856 9857 /** 9858 * @hide 9859 */ 9860 @Override areNotificationsVisiblyDifferent(Style other)9861 public boolean areNotificationsVisiblyDifferent(Style other) { 9862 if (other == null || getClass() != other.getClass()) { 9863 return true; 9864 } 9865 CallStyle otherS = (CallStyle) other; 9866 return !Objects.equals(mCallType, otherS.mCallType) 9867 || !Objects.equals(mPerson, otherS.mPerson) 9868 || !Objects.equals(mVerificationText, otherS.mVerificationText); 9869 } 9870 } 9871 9872 /** 9873 * Notification style for custom views that are decorated by the system 9874 * 9875 * <p>Instead of providing a notification that is completely custom, a developer can set this 9876 * style and still obtain system decorations like the notification header with the expand 9877 * affordance and actions. 9878 * 9879 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 9880 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 9881 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 9882 * corresponding custom views to display. 9883 * 9884 * To use this style with your Notification, feed it to 9885 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 9886 * <pre class="prettyprint"> 9887 * Notification noti = new Notification.Builder() 9888 * .setSmallIcon(R.drawable.ic_stat_player) 9889 * .setLargeIcon(albumArtBitmap)) 9890 * .setCustomContentView(contentView); 9891 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 9892 * .build(); 9893 * </pre> 9894 */ 9895 public static class DecoratedCustomViewStyle extends Style { 9896 DecoratedCustomViewStyle()9897 public DecoratedCustomViewStyle() { 9898 } 9899 9900 /** 9901 * @hide 9902 */ displayCustomViewInline()9903 public boolean displayCustomViewInline() { 9904 return true; 9905 } 9906 9907 /** 9908 * @hide 9909 */ 9910 @Override makeContentView(boolean increasedHeight)9911 public RemoteViews makeContentView(boolean increasedHeight) { 9912 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 9913 } 9914 9915 /** 9916 * @hide 9917 */ 9918 @Override makeBigContentView()9919 public RemoteViews makeBigContentView() { 9920 return makeDecoratedBigContentView(); 9921 } 9922 9923 /** 9924 * @hide 9925 */ 9926 @Override makeHeadsUpContentView(boolean increasedHeight)9927 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9928 return makeDecoratedHeadsUpContentView(); 9929 } 9930 makeDecoratedHeadsUpContentView()9931 private RemoteViews makeDecoratedHeadsUpContentView() { 9932 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 9933 ? mBuilder.mN.contentView 9934 : mBuilder.mN.headsUpContentView; 9935 if (headsUpContentView == null) { 9936 return null; // no custom view; use the default behavior 9937 } 9938 if (mBuilder.mActions.size() == 0) { 9939 return makeStandardTemplateWithCustomContent(headsUpContentView); 9940 } 9941 TemplateBindResult result = new TemplateBindResult(); 9942 StandardTemplateParams p = mBuilder.mParams.reset() 9943 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 9944 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9945 .fillTextsFrom(mBuilder); 9946 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 9947 mBuilder.getHeadsUpBaseLayoutResource(), p, result); 9948 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView, 9949 p, result); 9950 return remoteViews; 9951 } 9952 makeStandardTemplateWithCustomContent(RemoteViews customContent)9953 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 9954 if (customContent == null) { 9955 return null; // no custom view; use the default behavior 9956 } 9957 TemplateBindResult result = new TemplateBindResult(); 9958 StandardTemplateParams p = mBuilder.mParams.reset() 9959 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 9960 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9961 .fillTextsFrom(mBuilder); 9962 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 9963 mBuilder.getBaseLayoutResource(), p, result); 9964 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent, 9965 p, result); 9966 return remoteViews; 9967 } 9968 makeDecoratedBigContentView()9969 private RemoteViews makeDecoratedBigContentView() { 9970 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 9971 ? mBuilder.mN.contentView 9972 : mBuilder.mN.bigContentView; 9973 if (bigContentView == null) { 9974 return null; // no custom view; use the default behavior 9975 } 9976 TemplateBindResult result = new TemplateBindResult(); 9977 StandardTemplateParams p = mBuilder.mParams.reset() 9978 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9979 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 9980 .fillTextsFrom(mBuilder); 9981 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 9982 mBuilder.getBigBaseLayoutResource(), p, result); 9983 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView, 9984 p, result); 9985 return remoteViews; 9986 } 9987 9988 /** 9989 * @hide 9990 */ 9991 @Override areNotificationsVisiblyDifferent(Style other)9992 public boolean areNotificationsVisiblyDifferent(Style other) { 9993 if (other == null || getClass() != other.getClass()) { 9994 return true; 9995 } 9996 // Comparison done for all custom RemoteViews, independent of style 9997 return false; 9998 } 9999 } 10000 10001 /** 10002 * Notification style for media custom views that are decorated by the system 10003 * 10004 * <p>Instead of providing a media notification that is completely custom, a developer can set 10005 * this style and still obtain system decorations like the notification header with the expand 10006 * affordance and actions. 10007 * 10008 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 10009 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 10010 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 10011 * corresponding custom views to display. 10012 * <p> 10013 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 10014 * notification by using {@link Notification.Builder#setColorized(boolean)}. 10015 * <p> 10016 * To use this style with your Notification, feed it to 10017 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 10018 * <pre class="prettyprint"> 10019 * Notification noti = new Notification.Builder() 10020 * .setSmallIcon(R.drawable.ic_stat_player) 10021 * .setLargeIcon(albumArtBitmap)) 10022 * .setCustomContentView(contentView); 10023 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 10024 * .setMediaSession(mySession)) 10025 * .build(); 10026 * </pre> 10027 * 10028 * @see android.app.Notification.DecoratedCustomViewStyle 10029 * @see android.app.Notification.MediaStyle 10030 */ 10031 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 10032 DecoratedMediaCustomViewStyle()10033 public DecoratedMediaCustomViewStyle() { 10034 } 10035 10036 /** 10037 * @hide 10038 */ displayCustomViewInline()10039 public boolean displayCustomViewInline() { 10040 return true; 10041 } 10042 10043 /** 10044 * @hide 10045 */ 10046 @Override makeContentView(boolean increasedHeight)10047 public RemoteViews makeContentView(boolean increasedHeight) { 10048 return makeMediaContentView(mBuilder.mN.contentView); 10049 } 10050 10051 /** 10052 * @hide 10053 */ 10054 @Override makeBigContentView()10055 public RemoteViews makeBigContentView() { 10056 RemoteViews customContent = mBuilder.mN.bigContentView != null 10057 ? mBuilder.mN.bigContentView 10058 : mBuilder.mN.contentView; 10059 return makeMediaBigContentView(customContent); 10060 } 10061 10062 /** 10063 * @hide 10064 */ 10065 @Override makeHeadsUpContentView(boolean increasedHeight)10066 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 10067 RemoteViews customContent = mBuilder.mN.headsUpContentView != null 10068 ? mBuilder.mN.headsUpContentView 10069 : mBuilder.mN.contentView; 10070 return makeMediaBigContentView(customContent); 10071 } 10072 10073 /** 10074 * @hide 10075 */ 10076 @Override areNotificationsVisiblyDifferent(Style other)10077 public boolean areNotificationsVisiblyDifferent(Style other) { 10078 if (other == null || getClass() != other.getClass()) { 10079 return true; 10080 } 10081 // Comparison done for all custom RemoteViews, independent of style 10082 return false; 10083 } 10084 } 10085 10086 /** 10087 * Encapsulates the information needed to display a notification as a bubble. 10088 * 10089 * <p>A bubble is used to display app content in a floating window over the existing 10090 * foreground activity. A bubble has a collapsed state represented by an icon and an 10091 * expanded state that displays an activity. These may be defined via 10092 * {@link Builder#Builder(PendingIntent, Icon)} or they may 10093 * be defined via an existing shortcut using {@link Builder#Builder(String)}. 10094 * </p> 10095 * 10096 * <b>Notifications with a valid and allowed bubble will display in collapsed state 10097 * outside of the notification shade on unlocked devices. When a user interacts with the 10098 * collapsed bubble, the bubble activity will be invoked and displayed.</b> 10099 * 10100 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 10101 */ 10102 public static final class BubbleMetadata implements Parcelable { 10103 10104 private PendingIntent mPendingIntent; 10105 private PendingIntent mDeleteIntent; 10106 private Icon mIcon; 10107 private int mDesiredHeight; 10108 @DimenRes private int mDesiredHeightResId; 10109 private int mFlags; 10110 private String mShortcutId; 10111 10112 /** 10113 * If set and the app creating the bubble is in the foreground, the bubble will be posted 10114 * in its expanded state. 10115 * 10116 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 10117 * The app is considered foreground if it is visible and on the screen, note that 10118 * a foreground service does not qualify. 10119 * </p> 10120 * 10121 * <p>Generally this flag should only be set if the user has performed an action to request 10122 * or create a bubble.</p> 10123 * 10124 * @hide 10125 */ 10126 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 10127 10128 /** 10129 * Indicates whether the notification associated with the bubble is being visually 10130 * suppressed from the notification shade. When <code>true</code> the notification is 10131 * hidden, when <code>false</code> the notification shows as normal. 10132 * 10133 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 10134 * the associated notification in the notification shade.</p> 10135 * 10136 * <p>Generally this flag should only be set by the app if the user has performed an 10137 * action to request or create a bubble, or if the user has seen the content in the 10138 * notification and the notification is no longer relevant. </p> 10139 * 10140 * <p>The system will also update this flag with <code>true</code> to hide the notification 10141 * from the user once the bubble has been expanded. </p> 10142 * 10143 * @hide 10144 */ 10145 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 10146 10147 /** 10148 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 10149 * user is viewing the same content outside of the bubble. For example, the user has a 10150 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10151 * 10152 * @hide 10153 */ 10154 public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004; 10155 10156 /** 10157 * Indicates whether the bubble is visually suppressed from the bubble stack. 10158 * 10159 * @hide 10160 */ 10161 public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008; 10162 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)10163 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 10164 Icon icon, int height, @DimenRes int heightResId, String shortcutId) { 10165 mPendingIntent = expandIntent; 10166 mIcon = icon; 10167 mDesiredHeight = height; 10168 mDesiredHeightResId = heightResId; 10169 mDeleteIntent = deleteIntent; 10170 mShortcutId = shortcutId; 10171 } 10172 BubbleMetadata(Parcel in)10173 private BubbleMetadata(Parcel in) { 10174 if (in.readInt() != 0) { 10175 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 10176 } 10177 if (in.readInt() != 0) { 10178 mIcon = Icon.CREATOR.createFromParcel(in); 10179 } 10180 mDesiredHeight = in.readInt(); 10181 mFlags = in.readInt(); 10182 if (in.readInt() != 0) { 10183 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 10184 } 10185 mDesiredHeightResId = in.readInt(); 10186 if (in.readInt() != 0) { 10187 mShortcutId = in.readString8(); 10188 } 10189 } 10190 10191 /** 10192 * @return the shortcut id used for this bubble if created via 10193 * {@link Builder#Builder(String)} or null if created 10194 * via {@link Builder#Builder(PendingIntent, Icon)}. 10195 */ 10196 @Nullable getShortcutId()10197 public String getShortcutId() { 10198 return mShortcutId; 10199 } 10200 10201 /** 10202 * @return the pending intent used to populate the floating window for this bubble, or 10203 * null if this bubble is created via {@link Builder#Builder(String)}. 10204 */ 10205 @SuppressLint("InvalidNullConversion") 10206 @Nullable getIntent()10207 public PendingIntent getIntent() { 10208 return mPendingIntent; 10209 } 10210 10211 /** 10212 * @deprecated use {@link #getIntent()} instead. 10213 * @removed Removed from the R SDK but was never publicly stable. 10214 */ 10215 @Nullable 10216 @Deprecated getBubbleIntent()10217 public PendingIntent getBubbleIntent() { 10218 return mPendingIntent; 10219 } 10220 10221 /** 10222 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 10223 */ 10224 @Nullable getDeleteIntent()10225 public PendingIntent getDeleteIntent() { 10226 return mDeleteIntent; 10227 } 10228 10229 /** 10230 * @return the icon that will be displayed for this bubble when it is collapsed, or null 10231 * if the bubble is created via {@link Builder#Builder(String)}. 10232 */ 10233 @SuppressLint("InvalidNullConversion") 10234 @Nullable getIcon()10235 public Icon getIcon() { 10236 return mIcon; 10237 } 10238 10239 /** 10240 * @deprecated use {@link #getIcon()} instead. 10241 * @removed Removed from the R SDK but was never publicly stable. 10242 */ 10243 @Nullable 10244 @Deprecated getBubbleIcon()10245 public Icon getBubbleIcon() { 10246 return mIcon; 10247 } 10248 10249 /** 10250 * @return the ideal height, in DPs, for the floating window that app content defined by 10251 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has 10252 * not been set. 10253 */ 10254 @Dimension(unit = DP) getDesiredHeight()10255 public int getDesiredHeight() { 10256 return mDesiredHeight; 10257 } 10258 10259 /** 10260 * @return the resId of ideal height for the floating window that app content defined by 10261 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 10262 * been provided for the desired height. 10263 */ 10264 @DimenRes getDesiredHeightResId()10265 public int getDesiredHeightResId() { 10266 return mDesiredHeightResId; 10267 } 10268 10269 /** 10270 * @return whether this bubble should auto expand when it is posted. 10271 * 10272 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 10273 */ getAutoExpandBubble()10274 public boolean getAutoExpandBubble() { 10275 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 10276 } 10277 10278 /** 10279 * Indicates whether the notification associated with the bubble is being visually 10280 * suppressed from the notification shade. When <code>true</code> the notification is 10281 * hidden, when <code>false</code> the notification shows as normal. 10282 * 10283 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 10284 * the associated notification in the notification shade.</p> 10285 * 10286 * <p>Generally the app should only set this flag if the user has performed an 10287 * action to request or create a bubble, or if the user has seen the content in the 10288 * notification and the notification is no longer relevant. </p> 10289 * 10290 * <p>The system will update this flag with <code>true</code> to hide the notification 10291 * from the user once the bubble has been expanded.</p> 10292 * 10293 * @return whether this bubble should suppress the notification when it is posted. 10294 * 10295 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 10296 */ isNotificationSuppressed()10297 public boolean isNotificationSuppressed() { 10298 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 10299 } 10300 10301 /** 10302 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 10303 * user is viewing the same content outside of the bubble. For example, the user has a 10304 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10305 * 10306 * To match the activity and the bubble notification, the bubble notification should 10307 * have a locus id set that matches a locus id set on the activity. 10308 * 10309 * @return whether this bubble should be suppressed when the same content is visible 10310 * outside of the bubble. 10311 * 10312 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10313 */ isBubbleSuppressable()10314 public boolean isBubbleSuppressable() { 10315 return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0; 10316 } 10317 10318 /** 10319 * Indicates whether the bubble is currently visually suppressed from the bubble stack. 10320 * 10321 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 10322 */ isBubbleSuppressed()10323 public boolean isBubbleSuppressed() { 10324 return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0; 10325 } 10326 10327 /** 10328 * Sets whether the notification associated with the bubble is being visually 10329 * suppressed from the notification shade. When <code>true</code> the notification is 10330 * hidden, when <code>false</code> the notification shows as normal. 10331 * 10332 * @hide 10333 */ setSuppressNotification(boolean suppressed)10334 public void setSuppressNotification(boolean suppressed) { 10335 if (suppressed) { 10336 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10337 } else { 10338 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 10339 } 10340 } 10341 10342 /** 10343 * Sets whether the bubble should be visually suppressed from the bubble stack if the 10344 * user is viewing the same content outside of the bubble. For example, the user has a 10345 * bubble with Alice and then opens up the main app and navigates to Alice's page. 10346 * 10347 * @hide 10348 */ setSuppressBubble(boolean suppressed)10349 public void setSuppressBubble(boolean suppressed) { 10350 if (suppressed) { 10351 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10352 } else { 10353 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 10354 } 10355 } 10356 10357 /** 10358 * @hide 10359 */ setFlags(int flags)10360 public void setFlags(int flags) { 10361 mFlags = flags; 10362 } 10363 10364 /** 10365 * @hide 10366 */ getFlags()10367 public int getFlags() { 10368 return mFlags; 10369 } 10370 10371 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 10372 new Parcelable.Creator<BubbleMetadata>() { 10373 10374 @Override 10375 public BubbleMetadata createFromParcel(Parcel source) { 10376 return new BubbleMetadata(source); 10377 } 10378 10379 @Override 10380 public BubbleMetadata[] newArray(int size) { 10381 return new BubbleMetadata[size]; 10382 } 10383 }; 10384 10385 @Override describeContents()10386 public int describeContents() { 10387 return 0; 10388 } 10389 10390 @Override writeToParcel(Parcel out, int flags)10391 public void writeToParcel(Parcel out, int flags) { 10392 out.writeInt(mPendingIntent != null ? 1 : 0); 10393 if (mPendingIntent != null) { 10394 mPendingIntent.writeToParcel(out, 0); 10395 } 10396 out.writeInt(mIcon != null ? 1 : 0); 10397 if (mIcon != null) { 10398 mIcon.writeToParcel(out, 0); 10399 } 10400 out.writeInt(mDesiredHeight); 10401 out.writeInt(mFlags); 10402 out.writeInt(mDeleteIntent != null ? 1 : 0); 10403 if (mDeleteIntent != null) { 10404 mDeleteIntent.writeToParcel(out, 0); 10405 } 10406 out.writeInt(mDesiredHeightResId); 10407 out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); 10408 if (!TextUtils.isEmpty(mShortcutId)) { 10409 out.writeString8(mShortcutId); 10410 } 10411 } 10412 10413 /** 10414 * Builder to construct a {@link BubbleMetadata} object. 10415 */ 10416 public static final class Builder { 10417 10418 private PendingIntent mPendingIntent; 10419 private Icon mIcon; 10420 private int mDesiredHeight; 10421 @DimenRes private int mDesiredHeightResId; 10422 private int mFlags; 10423 private PendingIntent mDeleteIntent; 10424 private String mShortcutId; 10425 10426 /** 10427 * @deprecated use {@link Builder#Builder(String)} for a bubble created via a 10428 * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble 10429 * created via a {@link PendingIntent}. 10430 */ 10431 @Deprecated Builder()10432 public Builder() { 10433 } 10434 10435 /** 10436 * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create 10437 * a shortcut bubble, ensure that the shortcut associated with the provided 10438 * {@param shortcutId} is published as a dynamic shortcut that was built with 10439 * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your 10440 * notification will not be able to bubble. 10441 * 10442 * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p> 10443 * 10444 * <p>The shortcut activity will be used when the bubble is expanded. This will display 10445 * the shortcut activity in a floating window over the existing foreground activity.</p> 10446 * 10447 * <p>When the activity is launched from a bubble, 10448 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10449 * </p> 10450 * 10451 * <p>If the shortcut has not been published when the bubble notification is sent, 10452 * no bubble will be produced. If the shortcut is deleted while the bubble is active, 10453 * the bubble will be removed.</p> 10454 * 10455 * @throws NullPointerException if shortcutId is null. 10456 * 10457 * @see ShortcutInfo 10458 * @see ShortcutInfo.Builder#setLongLived(boolean) 10459 * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List) 10460 */ Builder(@onNull String shortcutId)10461 public Builder(@NonNull String shortcutId) { 10462 if (TextUtils.isEmpty(shortcutId)) { 10463 throw new NullPointerException("Bubble requires a non-null shortcut id"); 10464 } 10465 mShortcutId = shortcutId; 10466 } 10467 10468 /** 10469 * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon. 10470 * 10471 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10472 * should be representative of the content within the bubble. If your app produces 10473 * multiple bubbles, the icon should be unique for each of them.</p> 10474 * 10475 * <p>The intent that will be used when the bubble is expanded. This will display the 10476 * app content in a floating window over the existing foreground activity. The intent 10477 * should point to a resizable activity. </p> 10478 * 10479 * <p>When the activity is launched from a bubble, 10480 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 10481 * </p> 10482 * 10483 * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE. 10484 * 10485 * @throws NullPointerException if intent is null. 10486 * @throws NullPointerException if icon is null. 10487 */ Builder(@onNull PendingIntent intent, @NonNull Icon icon)10488 public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) { 10489 if (intent == null) { 10490 throw new NullPointerException("Bubble requires non-null pending intent"); 10491 } 10492 if (icon == null) { 10493 throw new NullPointerException("Bubbles require non-null icon"); 10494 } 10495 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10496 && icon.getType() != TYPE_URI) { 10497 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10498 + "TYPE_URI_ADAPTIVE_BITMAP. " 10499 + "In the future, using an icon of this type will be required."); 10500 } 10501 mPendingIntent = intent; 10502 mIcon = icon; 10503 } 10504 10505 /** 10506 * @deprecated use {@link Builder#Builder(String)} instead. 10507 * @removed Removed from the R SDK but was never publicly stable. 10508 */ 10509 @NonNull 10510 @Deprecated createShortcutBubble(@onNull String shortcutId)10511 public BubbleMetadata.Builder createShortcutBubble(@NonNull String shortcutId) { 10512 if (!TextUtils.isEmpty(shortcutId)) { 10513 // If shortcut id is set, we don't use these if they were previously set. 10514 mPendingIntent = null; 10515 mIcon = null; 10516 } 10517 mShortcutId = shortcutId; 10518 return this; 10519 } 10520 10521 /** 10522 * @deprecated use {@link Builder#Builder(PendingIntent, Icon)} instead. 10523 * @removed Removed from the R SDK but was never publicly stable. 10524 */ 10525 @NonNull 10526 @Deprecated createIntentBubble(@onNull PendingIntent intent, @NonNull Icon icon)10527 public BubbleMetadata.Builder createIntentBubble(@NonNull PendingIntent intent, 10528 @NonNull Icon icon) { 10529 if (intent == null) { 10530 throw new IllegalArgumentException("Bubble requires non-null pending intent"); 10531 } 10532 if (icon == null) { 10533 throw new IllegalArgumentException("Bubbles require non-null icon"); 10534 } 10535 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10536 && icon.getType() != TYPE_URI) { 10537 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10538 + "TYPE_URI_ADAPTIVE_BITMAP. " 10539 + "In the future, using an icon of this type will be required."); 10540 } 10541 mShortcutId = null; 10542 mPendingIntent = intent; 10543 mIcon = icon; 10544 return this; 10545 } 10546 10547 /** 10548 * Sets the intent for the bubble. 10549 * 10550 * <p>The intent that will be used when the bubble is expanded. This will display the 10551 * app content in a floating window over the existing foreground activity. The intent 10552 * should point to a resizable activity. </p> 10553 * 10554 * @throws NullPointerException if intent is null. 10555 * @throws IllegalStateException if this builder was created via 10556 * {@link Builder#Builder(String)}. 10557 */ 10558 @NonNull setIntent(@onNull PendingIntent intent)10559 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 10560 if (mShortcutId != null) { 10561 throw new IllegalStateException("Created as a shortcut bubble, cannot set a " 10562 + "PendingIntent. Consider using " 10563 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10564 } 10565 if (intent == null) { 10566 throw new NullPointerException("Bubble requires non-null pending intent"); 10567 } 10568 mPendingIntent = intent; 10569 return this; 10570 } 10571 10572 /** 10573 * Sets the icon for the bubble. Can only be used if the bubble was created 10574 * via {@link Builder#Builder(PendingIntent, Icon)}. 10575 * 10576 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 10577 * should be representative of the content within the bubble. If your app produces 10578 * multiple bubbles, the icon should be unique for each of them.</p> 10579 * 10580 * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI} 10581 * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p> 10582 * 10583 * @throws NullPointerException if icon is null. 10584 * @throws IllegalStateException if this builder was created via 10585 * {@link Builder#Builder(String)}. 10586 */ 10587 @NonNull setIcon(@onNull Icon icon)10588 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 10589 if (mShortcutId != null) { 10590 throw new IllegalStateException("Created as a shortcut bubble, cannot set an " 10591 + "Icon. Consider using " 10592 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 10593 } 10594 if (icon == null) { 10595 throw new NullPointerException("Bubbles require non-null icon"); 10596 } 10597 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 10598 && icon.getType() != TYPE_URI) { 10599 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 10600 + "TYPE_URI_ADAPTIVE_BITMAP. " 10601 + "In the future, using an icon of this type will be required."); 10602 } 10603 mIcon = icon; 10604 return this; 10605 } 10606 10607 /** 10608 * Sets the desired height in DPs for the expanded content of the bubble. 10609 * 10610 * <p>This height may not be respected if there is not enough space on the screen or if 10611 * the provided height is too small to be useful.</p> 10612 * 10613 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 10614 * previous value set will be cleared after calling this method, and this value will 10615 * be used instead.</p> 10616 * 10617 * <p>A desired height (in DPs or via resID) is optional.</p> 10618 * 10619 * @see #setDesiredHeightResId(int) 10620 */ 10621 @NonNull setDesiredHeight(@imensionunit = DP) int height)10622 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 10623 mDesiredHeight = Math.max(height, 0); 10624 mDesiredHeightResId = 0; 10625 return this; 10626 } 10627 10628 10629 /** 10630 * Sets the desired height via resId for the expanded content of the bubble. 10631 * 10632 * <p>This height may not be respected if there is not enough space on the screen or if 10633 * the provided height is too small to be useful.</p> 10634 * 10635 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 10636 * previous value set will be cleared after calling this method, and this value will 10637 * be used instead.</p> 10638 * 10639 * <p>A desired height (in DPs or via resID) is optional.</p> 10640 * 10641 * @see #setDesiredHeight(int) 10642 */ 10643 @NonNull setDesiredHeightResId(@imenRes int heightResId)10644 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 10645 mDesiredHeightResId = heightResId; 10646 mDesiredHeight = 0; 10647 return this; 10648 } 10649 10650 /** 10651 * Sets whether the bubble will be posted in its expanded state. 10652 * 10653 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 10654 * The app is considered foreground if it is visible and on the screen, note that 10655 * a foreground service does not qualify. 10656 * </p> 10657 * 10658 * <p>Generally, this flag should only be set if the user has performed an action to 10659 * request or create a bubble.</p> 10660 * 10661 * <p>Setting this flag is optional; it defaults to false.</p> 10662 */ 10663 @NonNull setAutoExpandBubble(boolean shouldExpand)10664 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 10665 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 10666 return this; 10667 } 10668 10669 /** 10670 * Sets whether the bubble will be posted <b>without</b> the associated notification in 10671 * the notification shade. 10672 * 10673 * <p>Generally, this flag should only be set if the user has performed an action to 10674 * request or create a bubble, or if the user has seen the content in the notification 10675 * and the notification is no longer relevant.</p> 10676 * 10677 * <p>Setting this flag is optional; it defaults to false.</p> 10678 */ 10679 @NonNull setSuppressNotification(boolean shouldSuppressNotif)10680 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 10681 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 10682 return this; 10683 } 10684 10685 /** 10686 * Indicates whether the bubble should be visually suppressed from the bubble stack if 10687 * the user is viewing the same content outside of the bubble. For example, the user has 10688 * a bubble with Alice and then opens up the main app and navigates to Alice's page. 10689 * 10690 * To match the activity and the bubble notification, the bubble notification should 10691 * have a locus id set that matches a locus id set on the activity. 10692 * 10693 * {@link Notification.Builder#setLocusId(LocusId)} 10694 * {@link Activity#setLocusContext(LocusId, Bundle)} 10695 */ 10696 @NonNull setSuppressableBubble(boolean suppressBubble)10697 public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) { 10698 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble); 10699 return this; 10700 } 10701 10702 /** 10703 * Sets an intent to send when this bubble is explicitly removed by the user. 10704 * 10705 * <p>Setting a delete intent is optional.</p> 10706 */ 10707 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)10708 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 10709 mDeleteIntent = deleteIntent; 10710 return this; 10711 } 10712 10713 /** 10714 * Creates the {@link BubbleMetadata} defined by this builder. 10715 * 10716 * @throws NullPointerException if required elements have not been set. 10717 */ 10718 @NonNull build()10719 public BubbleMetadata build() { 10720 if (mShortcutId == null && mPendingIntent == null) { 10721 throw new NullPointerException( 10722 "Must supply pending intent or shortcut to bubble"); 10723 } 10724 if (mShortcutId == null && mIcon == null) { 10725 throw new NullPointerException( 10726 "Must supply an icon or shortcut for the bubble"); 10727 } 10728 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 10729 mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId); 10730 data.setFlags(mFlags); 10731 return data; 10732 } 10733 10734 /** 10735 * @hide 10736 */ setFlag(int mask, boolean value)10737 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 10738 if (value) { 10739 mFlags |= mask; 10740 } else { 10741 mFlags &= ~mask; 10742 } 10743 return this; 10744 } 10745 } 10746 } 10747 10748 10749 // When adding a new Style subclass here, don't forget to update 10750 // Builder.getNotificationStyleClass. 10751 10752 /** 10753 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 10754 * metadata or change options on a notification builder. 10755 */ 10756 public interface Extender { 10757 /** 10758 * Apply this extender to a notification builder. 10759 * @param builder the builder to be modified. 10760 * @return the build object for chaining. 10761 */ extend(Builder builder)10762 public Builder extend(Builder builder); 10763 } 10764 10765 /** 10766 * Helper class to add wearable extensions to notifications. 10767 * <p class="note"> See 10768 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 10769 * for Android Wear</a> for more information on how to use this class. 10770 * <p> 10771 * To create a notification with wearable extensions: 10772 * <ol> 10773 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 10774 * properties. 10775 * <li>Create a {@link android.app.Notification.WearableExtender}. 10776 * <li>Set wearable-specific properties using the 10777 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 10778 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 10779 * notification. 10780 * <li>Post the notification to the notification system with the 10781 * {@code NotificationManager.notify(...)} methods. 10782 * </ol> 10783 * 10784 * <pre class="prettyprint"> 10785 * Notification notif = new Notification.Builder(mContext) 10786 * .setContentTitle("New mail from " + sender.toString()) 10787 * .setContentText(subject) 10788 * .setSmallIcon(R.drawable.new_mail) 10789 * .extend(new Notification.WearableExtender() 10790 * .setContentIcon(R.drawable.new_mail)) 10791 * .build(); 10792 * NotificationManager notificationManger = 10793 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 10794 * notificationManger.notify(0, notif);</pre> 10795 * 10796 * <p>Wearable extensions can be accessed on an existing notification by using the 10797 * {@code WearableExtender(Notification)} constructor, 10798 * and then using the {@code get} methods to access values. 10799 * 10800 * <pre class="prettyprint"> 10801 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 10802 * notification); 10803 * List<Notification> pages = wearableExtender.getPages();</pre> 10804 */ 10805 public static final class WearableExtender implements Extender { 10806 /** 10807 * Sentinel value for an action index that is unset. 10808 */ 10809 public static final int UNSET_ACTION_INDEX = -1; 10810 10811 /** 10812 * Size value for use with {@link #setCustomSizePreset} to show this notification with 10813 * default sizing. 10814 * <p>For custom display notifications created using {@link #setDisplayIntent}, 10815 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 10816 * on their content. 10817 * 10818 * @deprecated Display intents are no longer supported. 10819 */ 10820 @Deprecated 10821 public static final int SIZE_DEFAULT = 0; 10822 10823 /** 10824 * Size value for use with {@link #setCustomSizePreset} to show this notification 10825 * with an extra small size. 10826 * <p>This value is only applicable for custom display notifications created using 10827 * {@link #setDisplayIntent}. 10828 * 10829 * @deprecated Display intents are no longer supported. 10830 */ 10831 @Deprecated 10832 public static final int SIZE_XSMALL = 1; 10833 10834 /** 10835 * Size value for use with {@link #setCustomSizePreset} to show this notification 10836 * with a small size. 10837 * <p>This value is only applicable for custom display notifications created using 10838 * {@link #setDisplayIntent}. 10839 * 10840 * @deprecated Display intents are no longer supported. 10841 */ 10842 @Deprecated 10843 public static final int SIZE_SMALL = 2; 10844 10845 /** 10846 * Size value for use with {@link #setCustomSizePreset} to show this notification 10847 * with a medium size. 10848 * <p>This value is only applicable for custom display notifications created using 10849 * {@link #setDisplayIntent}. 10850 * 10851 * @deprecated Display intents are no longer supported. 10852 */ 10853 @Deprecated 10854 public static final int SIZE_MEDIUM = 3; 10855 10856 /** 10857 * Size value for use with {@link #setCustomSizePreset} to show this notification 10858 * with a large size. 10859 * <p>This value is only applicable for custom display notifications created using 10860 * {@link #setDisplayIntent}. 10861 * 10862 * @deprecated Display intents are no longer supported. 10863 */ 10864 @Deprecated 10865 public static final int SIZE_LARGE = 4; 10866 10867 /** 10868 * Size value for use with {@link #setCustomSizePreset} to show this notification 10869 * full screen. 10870 * <p>This value is only applicable for custom display notifications created using 10871 * {@link #setDisplayIntent}. 10872 * 10873 * @deprecated Display intents are no longer supported. 10874 */ 10875 @Deprecated 10876 public static final int SIZE_FULL_SCREEN = 5; 10877 10878 /** 10879 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 10880 * short amount of time when this notification is displayed on the screen. This 10881 * is the default value. 10882 * 10883 * @deprecated This feature is no longer supported. 10884 */ 10885 @Deprecated 10886 public static final int SCREEN_TIMEOUT_SHORT = 0; 10887 10888 /** 10889 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 10890 * for a longer amount of time when this notification is displayed on the screen. 10891 * 10892 * @deprecated This feature is no longer supported. 10893 */ 10894 @Deprecated 10895 public static final int SCREEN_TIMEOUT_LONG = -1; 10896 10897 /** Notification extra which contains wearable extensions */ 10898 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 10899 10900 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 10901 private static final String KEY_ACTIONS = "actions"; 10902 private static final String KEY_FLAGS = "flags"; 10903 static final String KEY_DISPLAY_INTENT = "displayIntent"; 10904 private static final String KEY_PAGES = "pages"; 10905 static final String KEY_BACKGROUND = "background"; 10906 private static final String KEY_CONTENT_ICON = "contentIcon"; 10907 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 10908 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 10909 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 10910 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 10911 private static final String KEY_GRAVITY = "gravity"; 10912 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 10913 private static final String KEY_DISMISSAL_ID = "dismissalId"; 10914 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 10915 10916 // Flags bitwise-ored to mFlags 10917 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 10918 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 10919 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 10920 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 10921 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 10922 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 10923 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 10924 10925 // Default value for flags integer 10926 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 10927 10928 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 10929 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 10930 10931 private ArrayList<Action> mActions = new ArrayList<Action>(); 10932 private int mFlags = DEFAULT_FLAGS; 10933 private PendingIntent mDisplayIntent; 10934 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 10935 private Bitmap mBackground; 10936 private int mContentIcon; 10937 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 10938 private int mContentActionIndex = UNSET_ACTION_INDEX; 10939 private int mCustomSizePreset = SIZE_DEFAULT; 10940 private int mCustomContentHeight; 10941 private int mGravity = DEFAULT_GRAVITY; 10942 private int mHintScreenTimeout; 10943 private String mDismissalId; 10944 private String mBridgeTag; 10945 10946 /** 10947 * Create a {@link android.app.Notification.WearableExtender} with default 10948 * options. 10949 */ WearableExtender()10950 public WearableExtender() { 10951 } 10952 WearableExtender(Notification notif)10953 public WearableExtender(Notification notif) { 10954 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 10955 if (wearableBundle != null) { 10956 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS); 10957 if (actions != null) { 10958 mActions.addAll(actions); 10959 } 10960 10961 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 10962 mDisplayIntent = wearableBundle.getParcelable( 10963 KEY_DISPLAY_INTENT, PendingIntent.class); 10964 10965 Notification[] pages = getParcelableArrayFromBundle( 10966 wearableBundle, KEY_PAGES, Notification.class); 10967 if (pages != null) { 10968 Collections.addAll(mPages, pages); 10969 } 10970 10971 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class); 10972 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 10973 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 10974 DEFAULT_CONTENT_ICON_GRAVITY); 10975 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 10976 UNSET_ACTION_INDEX); 10977 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 10978 SIZE_DEFAULT); 10979 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 10980 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 10981 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 10982 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 10983 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 10984 } 10985 } 10986 10987 /** 10988 * Apply wearable extensions to a notification that is being built. This is typically 10989 * called by the {@link android.app.Notification.Builder#extend} method of 10990 * {@link android.app.Notification.Builder}. 10991 */ 10992 @Override extend(Notification.Builder builder)10993 public Notification.Builder extend(Notification.Builder builder) { 10994 Bundle wearableBundle = new Bundle(); 10995 10996 if (!mActions.isEmpty()) { 10997 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 10998 } 10999 if (mFlags != DEFAULT_FLAGS) { 11000 wearableBundle.putInt(KEY_FLAGS, mFlags); 11001 } 11002 if (mDisplayIntent != null) { 11003 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 11004 } 11005 if (!mPages.isEmpty()) { 11006 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 11007 new Notification[mPages.size()])); 11008 } 11009 if (mBackground != null) { 11010 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 11011 } 11012 if (mContentIcon != 0) { 11013 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 11014 } 11015 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 11016 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 11017 } 11018 if (mContentActionIndex != UNSET_ACTION_INDEX) { 11019 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 11020 mContentActionIndex); 11021 } 11022 if (mCustomSizePreset != SIZE_DEFAULT) { 11023 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 11024 } 11025 if (mCustomContentHeight != 0) { 11026 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 11027 } 11028 if (mGravity != DEFAULT_GRAVITY) { 11029 wearableBundle.putInt(KEY_GRAVITY, mGravity); 11030 } 11031 if (mHintScreenTimeout != 0) { 11032 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 11033 } 11034 if (mDismissalId != null) { 11035 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 11036 } 11037 if (mBridgeTag != null) { 11038 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 11039 } 11040 11041 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 11042 return builder; 11043 } 11044 11045 @Override clone()11046 public WearableExtender clone() { 11047 WearableExtender that = new WearableExtender(); 11048 that.mActions = new ArrayList<Action>(this.mActions); 11049 that.mFlags = this.mFlags; 11050 that.mDisplayIntent = this.mDisplayIntent; 11051 that.mPages = new ArrayList<Notification>(this.mPages); 11052 that.mBackground = this.mBackground; 11053 that.mContentIcon = this.mContentIcon; 11054 that.mContentIconGravity = this.mContentIconGravity; 11055 that.mContentActionIndex = this.mContentActionIndex; 11056 that.mCustomSizePreset = this.mCustomSizePreset; 11057 that.mCustomContentHeight = this.mCustomContentHeight; 11058 that.mGravity = this.mGravity; 11059 that.mHintScreenTimeout = this.mHintScreenTimeout; 11060 that.mDismissalId = this.mDismissalId; 11061 that.mBridgeTag = this.mBridgeTag; 11062 return that; 11063 } 11064 11065 /** 11066 * Add a wearable action to this notification. 11067 * 11068 * <p>When wearable actions are added using this method, the set of actions that 11069 * show on a wearable device splits from devices that only show actions added 11070 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 11071 * of which actions display on different devices. 11072 * 11073 * @param action the action to add to this notification 11074 * @return this object for method chaining 11075 * @see android.app.Notification.Action 11076 */ addAction(Action action)11077 public WearableExtender addAction(Action action) { 11078 mActions.add(action); 11079 return this; 11080 } 11081 11082 /** 11083 * Adds wearable actions to this notification. 11084 * 11085 * <p>When wearable actions are added using this method, the set of actions that 11086 * show on a wearable device splits from devices that only show actions added 11087 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 11088 * of which actions display on different devices. 11089 * 11090 * @param actions the actions to add to this notification 11091 * @return this object for method chaining 11092 * @see android.app.Notification.Action 11093 */ addActions(List<Action> actions)11094 public WearableExtender addActions(List<Action> actions) { 11095 mActions.addAll(actions); 11096 return this; 11097 } 11098 11099 /** 11100 * Clear all wearable actions present on this builder. 11101 * @return this object for method chaining. 11102 * @see #addAction 11103 */ clearActions()11104 public WearableExtender clearActions() { 11105 mActions.clear(); 11106 return this; 11107 } 11108 11109 /** 11110 * Get the wearable actions present on this notification. 11111 */ getActions()11112 public List<Action> getActions() { 11113 return mActions; 11114 } 11115 11116 /** 11117 * Set an intent to launch inside of an activity view when displaying 11118 * this notification. The {@link PendingIntent} provided should be for an activity. 11119 * 11120 * <pre class="prettyprint"> 11121 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 11122 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 11123 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 11124 * Notification notif = new Notification.Builder(context) 11125 * .extend(new Notification.WearableExtender() 11126 * .setDisplayIntent(displayPendingIntent) 11127 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 11128 * .build();</pre> 11129 * 11130 * <p>The activity to launch needs to allow embedding, must be exported, and 11131 * should have an empty task affinity. It is also recommended to use the device 11132 * default light theme. 11133 * 11134 * <p>Example AndroidManifest.xml entry: 11135 * <pre class="prettyprint"> 11136 * <activity android:name="com.example.MyDisplayActivity" 11137 * android:exported="true" 11138 * android:allowEmbedded="true" 11139 * android:taskAffinity="" 11140 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 11141 * 11142 * @param intent the {@link PendingIntent} for an activity 11143 * @return this object for method chaining 11144 * @see android.app.Notification.WearableExtender#getDisplayIntent 11145 * @deprecated Display intents are no longer supported. 11146 */ 11147 @Deprecated setDisplayIntent(PendingIntent intent)11148 public WearableExtender setDisplayIntent(PendingIntent intent) { 11149 mDisplayIntent = intent; 11150 return this; 11151 } 11152 11153 /** 11154 * Get the intent to launch inside of an activity view when displaying this 11155 * notification. This {@code PendingIntent} should be for an activity. 11156 * 11157 * @deprecated Display intents are no longer supported. 11158 */ 11159 @Deprecated getDisplayIntent()11160 public PendingIntent getDisplayIntent() { 11161 return mDisplayIntent; 11162 } 11163 11164 /** 11165 * Add an additional page of content to display with this notification. The current 11166 * notification forms the first page, and pages added using this function form 11167 * subsequent pages. This field can be used to separate a notification into multiple 11168 * sections. 11169 * 11170 * @param page the notification to add as another page 11171 * @return this object for method chaining 11172 * @see android.app.Notification.WearableExtender#getPages 11173 * @deprecated Multiple content pages are no longer supported. 11174 */ 11175 @Deprecated addPage(Notification page)11176 public WearableExtender addPage(Notification page) { 11177 mPages.add(page); 11178 return this; 11179 } 11180 11181 /** 11182 * Add additional pages of content to display with this notification. The current 11183 * notification forms the first page, and pages added using this function form 11184 * subsequent pages. This field can be used to separate a notification into multiple 11185 * sections. 11186 * 11187 * @param pages a list of notifications 11188 * @return this object for method chaining 11189 * @see android.app.Notification.WearableExtender#getPages 11190 * @deprecated Multiple content pages are no longer supported. 11191 */ 11192 @Deprecated addPages(List<Notification> pages)11193 public WearableExtender addPages(List<Notification> pages) { 11194 mPages.addAll(pages); 11195 return this; 11196 } 11197 11198 /** 11199 * Clear all additional pages present on this builder. 11200 * @return this object for method chaining. 11201 * @see #addPage 11202 * @deprecated Multiple content pages are no longer supported. 11203 */ 11204 @Deprecated clearPages()11205 public WearableExtender clearPages() { 11206 mPages.clear(); 11207 return this; 11208 } 11209 11210 /** 11211 * Get the array of additional pages of content for displaying this notification. The 11212 * current notification forms the first page, and elements within this array form 11213 * subsequent pages. This field can be used to separate a notification into multiple 11214 * sections. 11215 * @return the pages for this notification 11216 * @deprecated Multiple content pages are no longer supported. 11217 */ 11218 @Deprecated getPages()11219 public List<Notification> getPages() { 11220 return mPages; 11221 } 11222 11223 /** 11224 * Set a background image to be displayed behind the notification content. 11225 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 11226 * will work with any notification style. 11227 * 11228 * @param background the background bitmap 11229 * @return this object for method chaining 11230 * @see android.app.Notification.WearableExtender#getBackground 11231 * @deprecated Background images are no longer supported. 11232 */ 11233 @Deprecated setBackground(Bitmap background)11234 public WearableExtender setBackground(Bitmap background) { 11235 mBackground = background; 11236 return this; 11237 } 11238 11239 /** 11240 * Get a background image to be displayed behind the notification content. 11241 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 11242 * will work with any notification style. 11243 * 11244 * @return the background image 11245 * @see android.app.Notification.WearableExtender#setBackground 11246 * @deprecated Background images are no longer supported. 11247 */ 11248 @Deprecated getBackground()11249 public Bitmap getBackground() { 11250 return mBackground; 11251 } 11252 11253 /** 11254 * Set an icon that goes with the content of this notification. 11255 */ 11256 @Deprecated setContentIcon(int icon)11257 public WearableExtender setContentIcon(int icon) { 11258 mContentIcon = icon; 11259 return this; 11260 } 11261 11262 /** 11263 * Get an icon that goes with the content of this notification. 11264 */ 11265 @Deprecated getContentIcon()11266 public int getContentIcon() { 11267 return mContentIcon; 11268 } 11269 11270 /** 11271 * Set the gravity that the content icon should have within the notification display. 11272 * Supported values include {@link android.view.Gravity#START} and 11273 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 11274 * @see #setContentIcon 11275 */ 11276 @Deprecated setContentIconGravity(int contentIconGravity)11277 public WearableExtender setContentIconGravity(int contentIconGravity) { 11278 mContentIconGravity = contentIconGravity; 11279 return this; 11280 } 11281 11282 /** 11283 * Get the gravity that the content icon should have within the notification display. 11284 * Supported values include {@link android.view.Gravity#START} and 11285 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 11286 * @see #getContentIcon 11287 */ 11288 @Deprecated getContentIconGravity()11289 public int getContentIconGravity() { 11290 return mContentIconGravity; 11291 } 11292 11293 /** 11294 * Set an action from this notification's actions as the primary action. If the action has a 11295 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 11296 * directly on the notification. 11297 * 11298 * @param actionIndex The index of the primary action. 11299 * If wearable actions were added to the main notification, this index 11300 * will apply to that list, otherwise it will apply to the regular 11301 * actions list. 11302 */ setContentAction(int actionIndex)11303 public WearableExtender setContentAction(int actionIndex) { 11304 mContentActionIndex = actionIndex; 11305 return this; 11306 } 11307 11308 /** 11309 * Get the index of the notification action, if any, that was specified as the primary 11310 * action. 11311 * 11312 * <p>If wearable specific actions were added to the main notification, this index will 11313 * apply to that list, otherwise it will apply to the regular actions list. 11314 * 11315 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 11316 */ getContentAction()11317 public int getContentAction() { 11318 return mContentActionIndex; 11319 } 11320 11321 /** 11322 * Set the gravity that this notification should have within the available viewport space. 11323 * Supported values include {@link android.view.Gravity#TOP}, 11324 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11325 * The default value is {@link android.view.Gravity#BOTTOM}. 11326 */ 11327 @Deprecated setGravity(int gravity)11328 public WearableExtender setGravity(int gravity) { 11329 mGravity = gravity; 11330 return this; 11331 } 11332 11333 /** 11334 * Get the gravity that this notification should have within the available viewport space. 11335 * Supported values include {@link android.view.Gravity#TOP}, 11336 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 11337 * The default value is {@link android.view.Gravity#BOTTOM}. 11338 */ 11339 @Deprecated getGravity()11340 public int getGravity() { 11341 return mGravity; 11342 } 11343 11344 /** 11345 * Set the custom size preset for the display of this notification out of the available 11346 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11347 * {@link #SIZE_LARGE}. 11348 * <p>Some custom size presets are only applicable for custom display notifications created 11349 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 11350 * documentation for the preset in question. See also 11351 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 11352 */ 11353 @Deprecated setCustomSizePreset(int sizePreset)11354 public WearableExtender setCustomSizePreset(int sizePreset) { 11355 mCustomSizePreset = sizePreset; 11356 return this; 11357 } 11358 11359 /** 11360 * Get the custom size preset for the display of this notification out of the available 11361 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 11362 * {@link #SIZE_LARGE}. 11363 * <p>Some custom size presets are only applicable for custom display notifications created 11364 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 11365 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 11366 */ 11367 @Deprecated getCustomSizePreset()11368 public int getCustomSizePreset() { 11369 return mCustomSizePreset; 11370 } 11371 11372 /** 11373 * Set the custom height in pixels for the display of this notification's content. 11374 * <p>This option is only available for custom display notifications created 11375 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 11376 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 11377 * {@link #getCustomContentHeight}. 11378 */ 11379 @Deprecated setCustomContentHeight(int height)11380 public WearableExtender setCustomContentHeight(int height) { 11381 mCustomContentHeight = height; 11382 return this; 11383 } 11384 11385 /** 11386 * Get the custom height in pixels for the display of this notification's content. 11387 * <p>This option is only available for custom display notifications created 11388 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 11389 * {@link #setCustomContentHeight}. 11390 */ 11391 @Deprecated getCustomContentHeight()11392 public int getCustomContentHeight() { 11393 return mCustomContentHeight; 11394 } 11395 11396 /** 11397 * Set whether the scrolling position for the contents of this notification should start 11398 * at the bottom of the contents instead of the top when the contents are too long to 11399 * display within the screen. Default is false (start scroll at the top). 11400 */ setStartScrollBottom(boolean startScrollBottom)11401 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 11402 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 11403 return this; 11404 } 11405 11406 /** 11407 * Get whether the scrolling position for the contents of this notification should start 11408 * at the bottom of the contents instead of the top when the contents are too long to 11409 * display within the screen. Default is false (start scroll at the top). 11410 */ getStartScrollBottom()11411 public boolean getStartScrollBottom() { 11412 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 11413 } 11414 11415 /** 11416 * Set whether the content intent is available when the wearable device is not connected 11417 * to a companion device. The user can still trigger this intent when the wearable device 11418 * is offline, but a visual hint will indicate that the content intent may not be available. 11419 * Defaults to true. 11420 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)11421 public WearableExtender setContentIntentAvailableOffline( 11422 boolean contentIntentAvailableOffline) { 11423 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 11424 return this; 11425 } 11426 11427 /** 11428 * Get whether the content intent is available when the wearable device is not connected 11429 * to a companion device. The user can still trigger this intent when the wearable device 11430 * is offline, but a visual hint will indicate that the content intent may not be available. 11431 * Defaults to true. 11432 */ getContentIntentAvailableOffline()11433 public boolean getContentIntentAvailableOffline() { 11434 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 11435 } 11436 11437 /** 11438 * Set a hint that this notification's icon should not be displayed. 11439 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 11440 * @return this object for method chaining 11441 */ 11442 @Deprecated setHintHideIcon(boolean hintHideIcon)11443 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 11444 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 11445 return this; 11446 } 11447 11448 /** 11449 * Get a hint that this notification's icon should not be displayed. 11450 * @return {@code true} if this icon should not be displayed, false otherwise. 11451 * The default value is {@code false} if this was never set. 11452 */ 11453 @Deprecated getHintHideIcon()11454 public boolean getHintHideIcon() { 11455 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 11456 } 11457 11458 /** 11459 * Set a visual hint that only the background image of this notification should be 11460 * displayed, and other semantic content should be hidden. This hint is only applicable 11461 * to sub-pages added using {@link #addPage}. 11462 */ 11463 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)11464 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 11465 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 11466 return this; 11467 } 11468 11469 /** 11470 * Get a visual hint that only the background image of this notification should be 11471 * displayed, and other semantic content should be hidden. This hint is only applicable 11472 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 11473 */ 11474 @Deprecated getHintShowBackgroundOnly()11475 public boolean getHintShowBackgroundOnly() { 11476 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 11477 } 11478 11479 /** 11480 * Set a hint that this notification's background should not be clipped if possible, 11481 * and should instead be resized to fully display on the screen, retaining the aspect 11482 * ratio of the image. This can be useful for images like barcodes or qr codes. 11483 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 11484 * @return this object for method chaining 11485 */ 11486 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)11487 public WearableExtender setHintAvoidBackgroundClipping( 11488 boolean hintAvoidBackgroundClipping) { 11489 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 11490 return this; 11491 } 11492 11493 /** 11494 * Get a hint that this notification's background should not be clipped if possible, 11495 * and should instead be resized to fully display on the screen, retaining the aspect 11496 * ratio of the image. This can be useful for images like barcodes or qr codes. 11497 * @return {@code true} if it's ok if the background is clipped on the screen, false 11498 * otherwise. The default value is {@code false} if this was never set. 11499 */ 11500 @Deprecated getHintAvoidBackgroundClipping()11501 public boolean getHintAvoidBackgroundClipping() { 11502 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 11503 } 11504 11505 /** 11506 * Set a hint that the screen should remain on for at least this duration when 11507 * this notification is displayed on the screen. 11508 * @param timeout The requested screen timeout in milliseconds. Can also be either 11509 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11510 * @return this object for method chaining 11511 */ 11512 @Deprecated setHintScreenTimeout(int timeout)11513 public WearableExtender setHintScreenTimeout(int timeout) { 11514 mHintScreenTimeout = timeout; 11515 return this; 11516 } 11517 11518 /** 11519 * Get the duration, in milliseconds, that the screen should remain on for 11520 * when this notification is displayed. 11521 * @return the duration in milliseconds if > 0, or either one of the sentinel values 11522 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 11523 */ 11524 @Deprecated getHintScreenTimeout()11525 public int getHintScreenTimeout() { 11526 return mHintScreenTimeout; 11527 } 11528 11529 /** 11530 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 11531 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11532 * qr codes, as well as other simple black-and-white tickets. 11533 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 11534 * @return this object for method chaining 11535 * @deprecated This feature is no longer supported. 11536 */ 11537 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)11538 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 11539 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 11540 return this; 11541 } 11542 11543 /** 11544 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 11545 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 11546 * qr codes, as well as other simple black-and-white tickets. 11547 * @return {@code true} if it should be displayed in ambient, false otherwise 11548 * otherwise. The default value is {@code false} if this was never set. 11549 * @deprecated This feature is no longer supported. 11550 */ 11551 @Deprecated getHintAmbientBigPicture()11552 public boolean getHintAmbientBigPicture() { 11553 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 11554 } 11555 11556 /** 11557 * Set a hint that this notification's content intent will launch an {@link Activity} 11558 * directly, telling the platform that it can generate the appropriate transitions. 11559 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 11560 * an activity and transitions should be generated, false otherwise. 11561 * @return this object for method chaining 11562 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)11563 public WearableExtender setHintContentIntentLaunchesActivity( 11564 boolean hintContentIntentLaunchesActivity) { 11565 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 11566 return this; 11567 } 11568 11569 /** 11570 * Get a hint that this notification's content intent will launch an {@link Activity} 11571 * directly, telling the platform that it can generate the appropriate transitions 11572 * @return {@code true} if the content intent will launch an activity and transitions should 11573 * be generated, false otherwise. The default value is {@code false} if this was never set. 11574 */ getHintContentIntentLaunchesActivity()11575 public boolean getHintContentIntentLaunchesActivity() { 11576 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 11577 } 11578 11579 /** 11580 * Sets the dismissal id for this notification. If a notification is posted with a 11581 * dismissal id, then when that notification is canceled, notifications on other wearables 11582 * and the paired Android phone having that same dismissal id will also be canceled. See 11583 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 11584 * Notifications</a> for more information. 11585 * @param dismissalId the dismissal id of the notification. 11586 * @return this object for method chaining 11587 */ setDismissalId(String dismissalId)11588 public WearableExtender setDismissalId(String dismissalId) { 11589 mDismissalId = dismissalId; 11590 return this; 11591 } 11592 11593 /** 11594 * Returns the dismissal id of the notification. 11595 * @return the dismissal id of the notification or null if it has not been set. 11596 */ getDismissalId()11597 public String getDismissalId() { 11598 return mDismissalId; 11599 } 11600 11601 /** 11602 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 11603 * posted from a phone to provide finer-grained control on what notifications are bridged 11604 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 11605 * Features to Notifications</a> for more information. 11606 * @param bridgeTag the bridge tag of the notification. 11607 * @return this object for method chaining 11608 */ setBridgeTag(String bridgeTag)11609 public WearableExtender setBridgeTag(String bridgeTag) { 11610 mBridgeTag = bridgeTag; 11611 return this; 11612 } 11613 11614 /** 11615 * Returns the bridge tag of the notification. 11616 * @return the bridge tag or null if not present. 11617 */ getBridgeTag()11618 public String getBridgeTag() { 11619 return mBridgeTag; 11620 } 11621 setFlag(int mask, boolean value)11622 private void setFlag(int mask, boolean value) { 11623 if (value) { 11624 mFlags |= mask; 11625 } else { 11626 mFlags &= ~mask; 11627 } 11628 } 11629 } 11630 11631 /** 11632 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 11633 * with car extensions: 11634 * 11635 * <ol> 11636 * <li>Create an {@link Notification.Builder}, setting any desired 11637 * properties. 11638 * <li>Create a {@link CarExtender}. 11639 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 11640 * {@link CarExtender}. 11641 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 11642 * to apply the extensions to a notification. 11643 * </ol> 11644 * 11645 * <pre class="prettyprint"> 11646 * Notification notification = new Notification.Builder(context) 11647 * ... 11648 * .extend(new CarExtender() 11649 * .set*(...)) 11650 * .build(); 11651 * </pre> 11652 * 11653 * <p>Car extensions can be accessed on an existing notification by using the 11654 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 11655 * to access values. 11656 */ 11657 public static final class CarExtender implements Extender { 11658 private static final String TAG = "CarExtender"; 11659 11660 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 11661 private static final String EXTRA_LARGE_ICON = "large_icon"; 11662 private static final String EXTRA_CONVERSATION = "car_conversation"; 11663 private static final String EXTRA_COLOR = "app_color"; 11664 11665 private Bitmap mLargeIcon; 11666 private UnreadConversation mUnreadConversation; 11667 private int mColor = Notification.COLOR_DEFAULT; 11668 11669 /** 11670 * Create a {@link CarExtender} with default options. 11671 */ CarExtender()11672 public CarExtender() { 11673 } 11674 11675 /** 11676 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 11677 * 11678 * @param notif The notification from which to copy options. 11679 */ CarExtender(Notification notif)11680 public CarExtender(Notification notif) { 11681 Bundle carBundle = notif.extras == null ? 11682 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 11683 if (carBundle != null) { 11684 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class); 11685 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 11686 11687 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 11688 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 11689 } 11690 } 11691 11692 /** 11693 * Apply car extensions to a notification that is being built. This is typically called by 11694 * the {@link Notification.Builder#extend(Notification.Extender)} 11695 * method of {@link Notification.Builder}. 11696 */ 11697 @Override extend(Notification.Builder builder)11698 public Notification.Builder extend(Notification.Builder builder) { 11699 Bundle carExtensions = new Bundle(); 11700 11701 if (mLargeIcon != null) { 11702 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 11703 } 11704 if (mColor != Notification.COLOR_DEFAULT) { 11705 carExtensions.putInt(EXTRA_COLOR, mColor); 11706 } 11707 11708 if (mUnreadConversation != null) { 11709 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 11710 carExtensions.putBundle(EXTRA_CONVERSATION, b); 11711 } 11712 11713 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 11714 return builder; 11715 } 11716 11717 /** 11718 * Sets the accent color to use when Android Auto presents the notification. 11719 * 11720 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 11721 * to accent the displayed notification. However, not all colors are acceptable in an 11722 * automotive setting. This method can be used to override the color provided in the 11723 * notification in such a situation. 11724 */ setColor(@olorInt int color)11725 public CarExtender setColor(@ColorInt int color) { 11726 mColor = color; 11727 return this; 11728 } 11729 11730 /** 11731 * Gets the accent color. 11732 * 11733 * @see #setColor 11734 */ 11735 @ColorInt getColor()11736 public int getColor() { 11737 return mColor; 11738 } 11739 11740 /** 11741 * Sets the large icon of the car notification. 11742 * 11743 * If no large icon is set in the extender, Android Auto will display the icon 11744 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 11745 * 11746 * @param largeIcon The large icon to use in the car notification. 11747 * @return This object for method chaining. 11748 */ setLargeIcon(Bitmap largeIcon)11749 public CarExtender setLargeIcon(Bitmap largeIcon) { 11750 mLargeIcon = largeIcon; 11751 return this; 11752 } 11753 11754 /** 11755 * Gets the large icon used in this car notification, or null if no icon has been set. 11756 * 11757 * @return The large icon for the car notification. 11758 * @see CarExtender#setLargeIcon 11759 */ getLargeIcon()11760 public Bitmap getLargeIcon() { 11761 return mLargeIcon; 11762 } 11763 11764 /** 11765 * Sets the unread conversation in a message notification. 11766 * 11767 * @param unreadConversation The unread part of the conversation this notification conveys. 11768 * @return This object for method chaining. 11769 */ setUnreadConversation(UnreadConversation unreadConversation)11770 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 11771 mUnreadConversation = unreadConversation; 11772 return this; 11773 } 11774 11775 /** 11776 * Returns the unread conversation conveyed by this notification. 11777 * @see #setUnreadConversation(UnreadConversation) 11778 */ getUnreadConversation()11779 public UnreadConversation getUnreadConversation() { 11780 return mUnreadConversation; 11781 } 11782 11783 /** 11784 * A class which holds the unread messages from a conversation. 11785 */ 11786 public static class UnreadConversation { 11787 private static final String KEY_AUTHOR = "author"; 11788 private static final String KEY_TEXT = "text"; 11789 private static final String KEY_MESSAGES = "messages"; 11790 static final String KEY_REMOTE_INPUT = "remote_input"; 11791 static final String KEY_ON_REPLY = "on_reply"; 11792 static final String KEY_ON_READ = "on_read"; 11793 private static final String KEY_PARTICIPANTS = "participants"; 11794 private static final String KEY_TIMESTAMP = "timestamp"; 11795 11796 private final String[] mMessages; 11797 private final RemoteInput mRemoteInput; 11798 private final PendingIntent mReplyPendingIntent; 11799 private final PendingIntent mReadPendingIntent; 11800 private final String[] mParticipants; 11801 private final long mLatestTimestamp; 11802 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)11803 UnreadConversation(String[] messages, RemoteInput remoteInput, 11804 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 11805 String[] participants, long latestTimestamp) { 11806 mMessages = messages; 11807 mRemoteInput = remoteInput; 11808 mReadPendingIntent = readPendingIntent; 11809 mReplyPendingIntent = replyPendingIntent; 11810 mParticipants = participants; 11811 mLatestTimestamp = latestTimestamp; 11812 } 11813 11814 /** 11815 * Gets the list of messages conveyed by this notification. 11816 */ getMessages()11817 public String[] getMessages() { 11818 return mMessages; 11819 } 11820 11821 /** 11822 * Gets the remote input that will be used to convey the response to a message list, or 11823 * null if no such remote input exists. 11824 */ getRemoteInput()11825 public RemoteInput getRemoteInput() { 11826 return mRemoteInput; 11827 } 11828 11829 /** 11830 * Gets the pending intent that will be triggered when the user replies to this 11831 * notification. 11832 */ getReplyPendingIntent()11833 public PendingIntent getReplyPendingIntent() { 11834 return mReplyPendingIntent; 11835 } 11836 11837 /** 11838 * Gets the pending intent that Android Auto will send after it reads aloud all messages 11839 * in this object's message list. 11840 */ getReadPendingIntent()11841 public PendingIntent getReadPendingIntent() { 11842 return mReadPendingIntent; 11843 } 11844 11845 /** 11846 * Gets the participants in the conversation. 11847 */ getParticipants()11848 public String[] getParticipants() { 11849 return mParticipants; 11850 } 11851 11852 /** 11853 * Gets the firs participant in the conversation. 11854 */ getParticipant()11855 public String getParticipant() { 11856 return mParticipants.length > 0 ? mParticipants[0] : null; 11857 } 11858 11859 /** 11860 * Gets the timestamp of the conversation. 11861 */ getLatestTimestamp()11862 public long getLatestTimestamp() { 11863 return mLatestTimestamp; 11864 } 11865 getBundleForUnreadConversation()11866 Bundle getBundleForUnreadConversation() { 11867 Bundle b = new Bundle(); 11868 String author = null; 11869 if (mParticipants != null && mParticipants.length > 1) { 11870 author = mParticipants[0]; 11871 } 11872 Parcelable[] messages = new Parcelable[mMessages.length]; 11873 for (int i = 0; i < messages.length; i++) { 11874 Bundle m = new Bundle(); 11875 m.putString(KEY_TEXT, mMessages[i]); 11876 m.putString(KEY_AUTHOR, author); 11877 messages[i] = m; 11878 } 11879 b.putParcelableArray(KEY_MESSAGES, messages); 11880 if (mRemoteInput != null) { 11881 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 11882 } 11883 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 11884 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 11885 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 11886 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 11887 return b; 11888 } 11889 getUnreadConversationFromBundle(Bundle b)11890 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 11891 if (b == null) { 11892 return null; 11893 } 11894 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES); 11895 String[] messages = null; 11896 if (parcelableMessages != null) { 11897 String[] tmp = new String[parcelableMessages.length]; 11898 boolean success = true; 11899 for (int i = 0; i < tmp.length; i++) { 11900 if (!(parcelableMessages[i] instanceof Bundle)) { 11901 success = false; 11902 break; 11903 } 11904 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 11905 if (tmp[i] == null) { 11906 success = false; 11907 break; 11908 } 11909 } 11910 if (success) { 11911 messages = tmp; 11912 } else { 11913 return null; 11914 } 11915 } 11916 11917 PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class); 11918 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class); 11919 11920 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class); 11921 11922 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 11923 if (participants == null || participants.length != 1) { 11924 return null; 11925 } 11926 11927 return new UnreadConversation(messages, 11928 remoteInput, 11929 onReply, 11930 onRead, 11931 participants, b.getLong(KEY_TIMESTAMP)); 11932 } 11933 }; 11934 11935 /** 11936 * Builder class for {@link CarExtender.UnreadConversation} objects. 11937 */ 11938 public static class Builder { 11939 private final List<String> mMessages = new ArrayList<String>(); 11940 private final String mParticipant; 11941 private RemoteInput mRemoteInput; 11942 private PendingIntent mReadPendingIntent; 11943 private PendingIntent mReplyPendingIntent; 11944 private long mLatestTimestamp; 11945 11946 /** 11947 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 11948 * 11949 * @param name The name of the other participant in the conversation. 11950 */ Builder(String name)11951 public Builder(String name) { 11952 mParticipant = name; 11953 } 11954 11955 /** 11956 * Appends a new unread message to the list of messages for this conversation. 11957 * 11958 * The messages should be added from oldest to newest. 11959 * 11960 * @param message The text of the new unread message. 11961 * @return This object for method chaining. 11962 */ addMessage(String message)11963 public Builder addMessage(String message) { 11964 mMessages.add(message); 11965 return this; 11966 } 11967 11968 /** 11969 * Sets the pending intent and remote input which will convey the reply to this 11970 * notification. 11971 * 11972 * @param pendingIntent The pending intent which will be triggered on a reply. 11973 * @param remoteInput The remote input parcelable which will carry the reply. 11974 * @return This object for method chaining. 11975 * 11976 * @see CarExtender.UnreadConversation#getRemoteInput 11977 * @see CarExtender.UnreadConversation#getReplyPendingIntent 11978 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)11979 public Builder setReplyAction( 11980 PendingIntent pendingIntent, RemoteInput remoteInput) { 11981 mRemoteInput = remoteInput; 11982 mReplyPendingIntent = pendingIntent; 11983 11984 return this; 11985 } 11986 11987 /** 11988 * Sets the pending intent that will be sent once the messages in this notification 11989 * are read. 11990 * 11991 * @param pendingIntent The pending intent to use. 11992 * @return This object for method chaining. 11993 */ setReadPendingIntent(PendingIntent pendingIntent)11994 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 11995 mReadPendingIntent = pendingIntent; 11996 return this; 11997 } 11998 11999 /** 12000 * Sets the timestamp of the most recent message in an unread conversation. 12001 * 12002 * If a messaging notification has been posted by your application and has not 12003 * yet been cancelled, posting a later notification with the same id and tag 12004 * but without a newer timestamp may result in Android Auto not displaying a 12005 * heads up notification for the later notification. 12006 * 12007 * @param timestamp The timestamp of the most recent message in the conversation. 12008 * @return This object for method chaining. 12009 */ setLatestTimestamp(long timestamp)12010 public Builder setLatestTimestamp(long timestamp) { 12011 mLatestTimestamp = timestamp; 12012 return this; 12013 } 12014 12015 /** 12016 * Builds a new unread conversation object. 12017 * 12018 * @return The new unread conversation object. 12019 */ build()12020 public UnreadConversation build() { 12021 String[] messages = mMessages.toArray(new String[mMessages.size()]); 12022 String[] participants = { mParticipant }; 12023 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 12024 mReadPendingIntent, participants, mLatestTimestamp); 12025 } 12026 } 12027 } 12028 12029 /** 12030 * <p>Helper class to add Android TV extensions to notifications. To create a notification 12031 * with a TV extension: 12032 * 12033 * <ol> 12034 * <li>Create an {@link Notification.Builder}, setting any desired properties. 12035 * <li>Create a {@link TvExtender}. 12036 * <li>Set TV-specific properties using the {@code set} methods of 12037 * {@link TvExtender}. 12038 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 12039 * to apply the extension to a notification. 12040 * </ol> 12041 * 12042 * <pre class="prettyprint"> 12043 * Notification notification = new Notification.Builder(context) 12044 * ... 12045 * .extend(new TvExtender() 12046 * .set*(...)) 12047 * .build(); 12048 * </pre> 12049 * 12050 * <p>TV extensions can be accessed on an existing notification by using the 12051 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 12052 * to access values. 12053 * 12054 * @hide 12055 */ 12056 @SystemApi 12057 public static final class TvExtender implements Extender { 12058 private static final String TAG = "TvExtender"; 12059 12060 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 12061 private static final String EXTRA_FLAGS = "flags"; 12062 static final String EXTRA_CONTENT_INTENT = "content_intent"; 12063 static final String EXTRA_DELETE_INTENT = "delete_intent"; 12064 private static final String EXTRA_CHANNEL_ID = "channel_id"; 12065 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 12066 12067 // Flags bitwise-ored to mFlags 12068 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 12069 12070 private int mFlags; 12071 private String mChannelId; 12072 private PendingIntent mContentIntent; 12073 private PendingIntent mDeleteIntent; 12074 private boolean mSuppressShowOverApps; 12075 12076 /** 12077 * Create a {@link TvExtender} with default options. 12078 */ TvExtender()12079 public TvExtender() { 12080 mFlags = FLAG_AVAILABLE_ON_TV; 12081 } 12082 12083 /** 12084 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 12085 * 12086 * @param notif The notification from which to copy options. 12087 */ TvExtender(Notification notif)12088 public TvExtender(Notification notif) { 12089 Bundle bundle = notif.extras == null ? 12090 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 12091 if (bundle != null) { 12092 mFlags = bundle.getInt(EXTRA_FLAGS); 12093 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 12094 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 12095 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class); 12096 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class); 12097 } 12098 } 12099 12100 /** 12101 * Apply a TV extension to a notification that is being built. This is typically called by 12102 * the {@link Notification.Builder#extend(Notification.Extender)} 12103 * method of {@link Notification.Builder}. 12104 */ 12105 @Override extend(Notification.Builder builder)12106 public Notification.Builder extend(Notification.Builder builder) { 12107 Bundle bundle = new Bundle(); 12108 12109 bundle.putInt(EXTRA_FLAGS, mFlags); 12110 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 12111 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 12112 if (mContentIntent != null) { 12113 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 12114 } 12115 12116 if (mDeleteIntent != null) { 12117 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 12118 } 12119 12120 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 12121 return builder; 12122 } 12123 12124 /** 12125 * Returns true if this notification should be shown on TV. This method return true 12126 * if the notification was extended with a TvExtender. 12127 */ isAvailableOnTv()12128 public boolean isAvailableOnTv() { 12129 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 12130 } 12131 12132 /** 12133 * Specifies the channel the notification should be delivered on when shown on TV. 12134 * It can be different from the channel that the notification is delivered to when 12135 * posting on a non-TV device. 12136 */ setChannel(String channelId)12137 public TvExtender setChannel(String channelId) { 12138 mChannelId = channelId; 12139 return this; 12140 } 12141 12142 /** 12143 * Specifies the channel the notification should be delivered on when shown on TV. 12144 * It can be different from the channel that the notification is delivered to when 12145 * posting on a non-TV device. 12146 */ setChannelId(String channelId)12147 public TvExtender setChannelId(String channelId) { 12148 mChannelId = channelId; 12149 return this; 12150 } 12151 12152 /** @removed */ 12153 @Deprecated getChannel()12154 public String getChannel() { 12155 return mChannelId; 12156 } 12157 12158 /** 12159 * Returns the id of the channel this notification posts to on TV. 12160 */ getChannelId()12161 public String getChannelId() { 12162 return mChannelId; 12163 } 12164 12165 /** 12166 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 12167 * If provided, it is used instead of the content intent specified 12168 * at the level of Notification. 12169 */ setContentIntent(PendingIntent intent)12170 public TvExtender setContentIntent(PendingIntent intent) { 12171 mContentIntent = intent; 12172 return this; 12173 } 12174 12175 /** 12176 * Returns the TV-specific content intent. If this method returns null, the 12177 * main content intent on the notification should be used. 12178 * 12179 * @see {@link Notification#contentIntent} 12180 */ getContentIntent()12181 public PendingIntent getContentIntent() { 12182 return mContentIntent; 12183 } 12184 12185 /** 12186 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 12187 * by the user on TV. If provided, it is used instead of the delete intent specified 12188 * at the level of Notification. 12189 */ setDeleteIntent(PendingIntent intent)12190 public TvExtender setDeleteIntent(PendingIntent intent) { 12191 mDeleteIntent = intent; 12192 return this; 12193 } 12194 12195 /** 12196 * Returns the TV-specific delete intent. If this method returns null, the 12197 * main delete intent on the notification should be used. 12198 * 12199 * @see {@link Notification#deleteIntent} 12200 */ getDeleteIntent()12201 public PendingIntent getDeleteIntent() { 12202 return mDeleteIntent; 12203 } 12204 12205 /** 12206 * Specifies whether this notification should suppress showing a message over top of apps 12207 * outside of the launcher. 12208 */ setSuppressShowOverApps(boolean suppress)12209 public TvExtender setSuppressShowOverApps(boolean suppress) { 12210 mSuppressShowOverApps = suppress; 12211 return this; 12212 } 12213 12214 /** 12215 * Returns true if this notification should not show messages over top of apps 12216 * outside of the launcher. 12217 */ getSuppressShowOverApps()12218 public boolean getSuppressShowOverApps() { 12219 return mSuppressShowOverApps; 12220 } 12221 } 12222 12223 /** 12224 * Get an array of Parcelable objects from a parcelable array bundle field. 12225 * Update the bundle to have a typed array so fetches in the future don't need 12226 * to do an array copy. 12227 */ 12228 @Nullable getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)12229 private static <T extends Parcelable> T[] getParcelableArrayFromBundle( 12230 Bundle bundle, String key, Class<T> itemClass) { 12231 final Parcelable[] array = bundle.getParcelableArray(key); 12232 final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); 12233 if (arrayClass.isInstance(array) || array == null) { 12234 return (T[]) array; 12235 } 12236 final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); 12237 for (int i = 0; i < array.length; i++) { 12238 typedArray[i] = (T) array[i]; 12239 } 12240 bundle.putParcelableArray(key, typedArray); 12241 return typedArray; 12242 } 12243 12244 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)12245 public BuilderRemoteViews(Parcel parcel) { 12246 super(parcel); 12247 } 12248 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)12249 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 12250 super(appInfo, layoutId); 12251 } 12252 12253 @Override clone()12254 public BuilderRemoteViews clone() { 12255 Parcel p = Parcel.obtain(); 12256 writeToParcel(p, 0); 12257 p.setDataPosition(0); 12258 BuilderRemoteViews brv = new BuilderRemoteViews(p); 12259 p.recycle(); 12260 return brv; 12261 } 12262 12263 /** 12264 * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden. 12265 * 12266 * @see RemoteViews#shouldUseStaticFilter() 12267 */ 12268 @Override shouldUseStaticFilter()12269 protected boolean shouldUseStaticFilter() { 12270 return true; 12271 } 12272 } 12273 12274 /** 12275 * A result object where information about the template that was created is saved. 12276 */ 12277 private static class TemplateBindResult { 12278 boolean mRightIconVisible; 12279 float mRightIconWidthDp; 12280 float mRightIconHeightDp; 12281 12282 /** 12283 * The margin end that needs to be added to the heading so that it won't overlap 12284 * with the large icon. This value includes the space required to accommodate the large 12285 * icon, but should be added to the space needed to accommodate the expander. This does 12286 * not include the 16dp content margin that all notification views must have. 12287 */ 12288 public final MarginSet mHeadingExtraMarginSet = new MarginSet(); 12289 12290 /** 12291 * The margin end that needs to be added to the heading so that it won't overlap 12292 * with the large icon. This value includes the space required to accommodate the large 12293 * icon as well as the expander. This does not include the 16dp content margin that all 12294 * notification views must have. 12295 */ 12296 public final MarginSet mHeadingFullMarginSet = new MarginSet(); 12297 12298 /** 12299 * The margin end that needs to be added to the title text of the big state 12300 * so that it won't overlap with the large icon, but assuming the text can run under 12301 * the expander when that icon is not visible. 12302 */ 12303 public final MarginSet mTitleMarginSet = new MarginSet(); 12304 setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)12305 public void setRightIconState(boolean visible, float widthDp, float heightDp, 12306 float marginEndDpIfVisible, float expanderSizeDp) { 12307 mRightIconVisible = visible; 12308 mRightIconWidthDp = widthDp; 12309 mRightIconHeightDp = heightDp; 12310 mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible); 12311 mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp); 12312 mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp); 12313 } 12314 12315 /** 12316 * This contains the end margins for a view when the right icon is visible or not. These 12317 * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the 12318 * left_icon and adjust the margins, and to undo that change as well. 12319 */ 12320 private class MarginSet { 12321 private float mValueIfGone; 12322 private float mValueIfVisible; 12323 setValues(float valueIfGone, float valueIfVisible)12324 public void setValues(float valueIfGone, float valueIfVisible) { 12325 mValueIfGone = valueIfGone; 12326 mValueIfVisible = valueIfVisible; 12327 } 12328 applyToView(@onNull RemoteViews views, @IdRes int viewId)12329 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) { 12330 applyToView(views, viewId, 0); 12331 } 12332 applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)12333 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId, 12334 float extraMarginDp) { 12335 final float marginEndDp = getDpValue() + extraMarginDp; 12336 if (viewId == R.id.notification_header) { 12337 views.setFloat(R.id.notification_header, 12338 "setTopLineExtraMarginEndDp", marginEndDp); 12339 } else if (viewId == R.id.text || viewId == R.id.big_text) { 12340 if (mValueIfGone != 0) { 12341 throw new RuntimeException("Programming error: `text` and `big_text` use " 12342 + "ImageFloatingTextView which can either show a margin or not; " 12343 + "thus mValueIfGone must be 0, but it was " + mValueIfGone); 12344 } 12345 // Note that the caller must set "setNumIndentLines" to a positive int in order 12346 // for this margin to do anything at all. 12347 views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible); 12348 views.setBoolean(viewId, "setHasImage", mRightIconVisible); 12349 // Apply just the *extra* margin as the view layout margin; this will be 12350 // unchanged depending on the visibility of the image, but it means that the 12351 // extra margin applies to *every* line of text instead of just indented lines. 12352 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12353 extraMarginDp, TypedValue.COMPLEX_UNIT_DIP); 12354 } else { 12355 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 12356 marginEndDp, TypedValue.COMPLEX_UNIT_DIP); 12357 } 12358 if (mRightIconVisible) { 12359 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, 12360 TypedValue.createComplexDimension( 12361 mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12362 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone, 12363 TypedValue.createComplexDimension( 12364 mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 12365 } 12366 } 12367 getDpValue()12368 public float getDpValue() { 12369 return mRightIconVisible ? mValueIfVisible : mValueIfGone; 12370 } 12371 } 12372 } 12373 12374 private static class StandardTemplateParams { 12375 /** 12376 * Notifications will be minimally decorated with ONLY an icon and expander: 12377 * <li>A large icon is never shown. 12378 * <li>A progress bar is never shown. 12379 * <li>The expanded and heads up states do not show actions, even if provided. 12380 */ 12381 public static final int DECORATION_MINIMAL = 1; 12382 12383 /** 12384 * Notifications will be partially decorated with AT LEAST an icon and expander: 12385 * <li>A large icon is shown if provided. 12386 * <li>A progress bar is shown if provided and enough space remains below the content. 12387 * <li>Actions are shown in the expanded and heads up states. 12388 */ 12389 public static final int DECORATION_PARTIAL = 2; 12390 12391 public static int VIEW_TYPE_UNSPECIFIED = 0; 12392 public static int VIEW_TYPE_NORMAL = 1; 12393 public static int VIEW_TYPE_BIG = 2; 12394 public static int VIEW_TYPE_HEADS_UP = 3; 12395 public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state 12396 public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version 12397 public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group 12398 12399 int mViewType = VIEW_TYPE_UNSPECIFIED; 12400 boolean mHeaderless; 12401 boolean mHideAppName; 12402 boolean mHideTitle; 12403 boolean mHideSubText; 12404 boolean mHideTime; 12405 boolean mHideActions; 12406 boolean mHideProgress; 12407 boolean mHideSnoozeButton; 12408 boolean mHideLeftIcon; 12409 boolean mHideRightIcon; 12410 Icon mPromotedPicture; 12411 boolean mCallStyleActions; 12412 boolean mAllowTextWithProgress; 12413 int mTitleViewId; 12414 int mTextViewId; 12415 CharSequence title; 12416 CharSequence text; 12417 CharSequence headerTextSecondary; 12418 CharSequence summaryText; 12419 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12420 boolean allowColorization = true; 12421 boolean mHighlightExpander = false; 12422 reset()12423 final StandardTemplateParams reset() { 12424 mViewType = VIEW_TYPE_UNSPECIFIED; 12425 mHeaderless = false; 12426 mHideAppName = false; 12427 mHideTitle = false; 12428 mHideSubText = false; 12429 mHideTime = false; 12430 mHideActions = false; 12431 mHideProgress = false; 12432 mHideSnoozeButton = false; 12433 mHideLeftIcon = false; 12434 mHideRightIcon = false; 12435 mPromotedPicture = null; 12436 mCallStyleActions = false; 12437 mAllowTextWithProgress = false; 12438 mTitleViewId = R.id.title; 12439 mTextViewId = R.id.text; 12440 title = null; 12441 text = null; 12442 summaryText = null; 12443 headerTextSecondary = null; 12444 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 12445 allowColorization = true; 12446 mHighlightExpander = false; 12447 return this; 12448 } 12449 hasTitle()12450 final boolean hasTitle() { 12451 return !TextUtils.isEmpty(title) && !mHideTitle; 12452 } 12453 viewType(int viewType)12454 final StandardTemplateParams viewType(int viewType) { 12455 mViewType = viewType; 12456 return this; 12457 } 12458 headerless(boolean headerless)12459 public StandardTemplateParams headerless(boolean headerless) { 12460 mHeaderless = headerless; 12461 return this; 12462 } 12463 hideAppName(boolean hideAppName)12464 public StandardTemplateParams hideAppName(boolean hideAppName) { 12465 mHideAppName = hideAppName; 12466 return this; 12467 } 12468 hideSubText(boolean hideSubText)12469 public StandardTemplateParams hideSubText(boolean hideSubText) { 12470 mHideSubText = hideSubText; 12471 return this; 12472 } 12473 hideTime(boolean hideTime)12474 public StandardTemplateParams hideTime(boolean hideTime) { 12475 mHideTime = hideTime; 12476 return this; 12477 } 12478 hideActions(boolean hideActions)12479 final StandardTemplateParams hideActions(boolean hideActions) { 12480 this.mHideActions = hideActions; 12481 return this; 12482 } 12483 hideProgress(boolean hideProgress)12484 final StandardTemplateParams hideProgress(boolean hideProgress) { 12485 this.mHideProgress = hideProgress; 12486 return this; 12487 } 12488 hideTitle(boolean hideTitle)12489 final StandardTemplateParams hideTitle(boolean hideTitle) { 12490 this.mHideTitle = hideTitle; 12491 return this; 12492 } 12493 callStyleActions(boolean callStyleActions)12494 final StandardTemplateParams callStyleActions(boolean callStyleActions) { 12495 this.mCallStyleActions = callStyleActions; 12496 return this; 12497 } 12498 allowTextWithProgress(boolean allowTextWithProgress)12499 final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) { 12500 this.mAllowTextWithProgress = allowTextWithProgress; 12501 return this; 12502 } 12503 hideSnoozeButton(boolean hideSnoozeButton)12504 final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) { 12505 this.mHideSnoozeButton = hideSnoozeButton; 12506 return this; 12507 } 12508 promotedPicture(Icon promotedPicture)12509 final StandardTemplateParams promotedPicture(Icon promotedPicture) { 12510 this.mPromotedPicture = promotedPicture; 12511 return this; 12512 } 12513 titleViewId(int titleViewId)12514 public StandardTemplateParams titleViewId(int titleViewId) { 12515 mTitleViewId = titleViewId; 12516 return this; 12517 } 12518 textViewId(int textViewId)12519 public StandardTemplateParams textViewId(int textViewId) { 12520 mTextViewId = textViewId; 12521 return this; 12522 } 12523 title(CharSequence title)12524 final StandardTemplateParams title(CharSequence title) { 12525 this.title = title; 12526 return this; 12527 } 12528 text(CharSequence text)12529 final StandardTemplateParams text(CharSequence text) { 12530 this.text = text; 12531 return this; 12532 } 12533 summaryText(CharSequence text)12534 final StandardTemplateParams summaryText(CharSequence text) { 12535 this.summaryText = text; 12536 return this; 12537 } 12538 headerTextSecondary(CharSequence text)12539 final StandardTemplateParams headerTextSecondary(CharSequence text) { 12540 this.headerTextSecondary = text; 12541 return this; 12542 } 12543 12544 hideLeftIcon(boolean hideLeftIcon)12545 final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) { 12546 this.mHideLeftIcon = hideLeftIcon; 12547 return this; 12548 } 12549 hideRightIcon(boolean hideRightIcon)12550 final StandardTemplateParams hideRightIcon(boolean hideRightIcon) { 12551 this.mHideRightIcon = hideRightIcon; 12552 return this; 12553 } 12554 disallowColorization()12555 final StandardTemplateParams disallowColorization() { 12556 this.allowColorization = false; 12557 return this; 12558 } 12559 highlightExpander(boolean highlight)12560 final StandardTemplateParams highlightExpander(boolean highlight) { 12561 this.mHighlightExpander = highlight; 12562 return this; 12563 } 12564 fillTextsFrom(Builder b)12565 final StandardTemplateParams fillTextsFrom(Builder b) { 12566 Bundle extras = b.mN.extras; 12567 this.title = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 12568 this.text = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 12569 this.summaryText = extras.getCharSequence(EXTRA_SUB_TEXT); 12570 return this; 12571 } 12572 12573 /** 12574 * Set the maximum lines of remote input history lines allowed. 12575 * @param maxRemoteInputHistory The number of lines. 12576 * @return The builder for method chaining. 12577 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)12578 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 12579 this.maxRemoteInputHistory = maxRemoteInputHistory; 12580 return this; 12581 } 12582 decorationType(int decorationType)12583 public StandardTemplateParams decorationType(int decorationType) { 12584 hideTitle(true); 12585 // Minimally decorated custom views do not show certain pieces of chrome that have 12586 // always been shown when using DecoratedCustomViewStyle. 12587 boolean hideOtherFields = decorationType <= DECORATION_MINIMAL; 12588 hideLeftIcon(false); // The left icon decoration is better than showing nothing. 12589 hideRightIcon(hideOtherFields); 12590 hideProgress(hideOtherFields); 12591 hideActions(hideOtherFields); 12592 return this; 12593 } 12594 } 12595 12596 /** 12597 * A utility which stores and calculates the palette of colors used to color notifications. 12598 * @hide 12599 */ 12600 @VisibleForTesting 12601 public static class Colors { 12602 private int mPaletteIsForRawColor = COLOR_INVALID; 12603 private boolean mPaletteIsForColorized = false; 12604 private boolean mPaletteIsForNightMode = false; 12605 // The following colors are the palette 12606 private int mBackgroundColor = COLOR_INVALID; 12607 private int mProtectionColor = COLOR_INVALID; 12608 private int mPrimaryTextColor = COLOR_INVALID; 12609 private int mSecondaryTextColor = COLOR_INVALID; 12610 private int mPrimaryAccentColor = COLOR_INVALID; 12611 private int mSecondaryAccentColor = COLOR_INVALID; 12612 private int mTertiaryAccentColor = COLOR_INVALID; 12613 private int mOnAccentTextColor = COLOR_INVALID; 12614 private int mErrorColor = COLOR_INVALID; 12615 private int mContrastColor = COLOR_INVALID; 12616 private int mRippleAlpha = 0x33; 12617 12618 /** 12619 * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which 12620 * returns null when the context is a mock with no theme. 12621 * 12622 * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper 12623 * instances can allocate as much as 5MB of memory, so its important to call this method 12624 * only when necessary, getting as many attributes as possible from each call. 12625 * 12626 * @see Resources.Theme#obtainStyledAttributes(int[]) 12627 */ 12628 @Nullable obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)12629 private static TypedArray obtainDayNightAttributes(@NonNull Context ctx, 12630 @NonNull @StyleableRes int[] attrs) { 12631 // when testing, the mock context may have no theme 12632 if (ctx.getTheme() == null) { 12633 return null; 12634 } 12635 Resources.Theme theme = new ContextThemeWrapper(ctx, 12636 R.style.Theme_DeviceDefault_DayNight).getTheme(); 12637 return theme.obtainStyledAttributes(attrs); 12638 } 12639 12640 /** A null-safe wrapper of TypedArray.getColor because mocks return null */ getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)12641 private static @ColorInt int getColor(@Nullable TypedArray ta, int index, 12642 @ColorInt int defValue) { 12643 return ta == null ? defValue : ta.getColor(index, defValue); 12644 } 12645 12646 /** 12647 * Resolve the palette. If the inputs have not changed, this will be a no-op. 12648 * This does not handle invalidating the resolved colors when the context itself changes, 12649 * because that case does not happen in the current notification inflation pipeline; we will 12650 * recreate a new builder (and thus a new palette) when reinflating notifications for a new 12651 * theme (admittedly, we do the same for night mode, but that's easy to check). 12652 * 12653 * @param ctx the builder context. 12654 * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha. 12655 * @param isColorized whether the notification is colorized. 12656 * @param nightMode whether the UI is in night mode. 12657 */ resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)12658 public void resolvePalette(Context ctx, int rawColor, 12659 boolean isColorized, boolean nightMode) { 12660 if (mPaletteIsForRawColor == rawColor 12661 && mPaletteIsForColorized == isColorized 12662 && mPaletteIsForNightMode == nightMode) { 12663 return; 12664 } 12665 mPaletteIsForRawColor = rawColor; 12666 mPaletteIsForColorized = isColorized; 12667 mPaletteIsForNightMode = nightMode; 12668 12669 if (isColorized) { 12670 if (rawColor == COLOR_DEFAULT) { 12671 int[] attrs = {R.attr.colorAccentSecondary}; 12672 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12673 mBackgroundColor = getColor(ta, 0, Color.WHITE); 12674 } 12675 } else { 12676 mBackgroundColor = rawColor; 12677 } 12678 mProtectionColor = COLOR_INVALID; // filled in at the end 12679 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12680 ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode), 12681 mBackgroundColor, 4.5); 12682 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 12683 ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode), 12684 mBackgroundColor, 4.5); 12685 mContrastColor = mPrimaryTextColor; 12686 mPrimaryAccentColor = mPrimaryTextColor; 12687 mSecondaryAccentColor = mSecondaryTextColor; 12688 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); 12689 mOnAccentTextColor = mBackgroundColor; 12690 mErrorColor = mPrimaryTextColor; 12691 mRippleAlpha = 0x33; 12692 } else { 12693 int[] attrs = { 12694 R.attr.colorSurface, 12695 R.attr.colorBackgroundFloating, 12696 R.attr.textColorPrimary, 12697 R.attr.textColorSecondary, 12698 R.attr.colorAccent, 12699 R.attr.colorAccentSecondary, 12700 R.attr.colorAccentTertiary, 12701 R.attr.textColorOnAccent, 12702 R.attr.colorError, 12703 R.attr.colorControlHighlight 12704 }; 12705 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 12706 mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE); 12707 mProtectionColor = getColor(ta, 1, COLOR_INVALID); 12708 mPrimaryTextColor = getColor(ta, 2, COLOR_INVALID); 12709 mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID); 12710 mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID); 12711 mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID); 12712 mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID); 12713 mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID); 12714 mErrorColor = getColor(ta, 8, COLOR_INVALID); 12715 mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff)); 12716 } 12717 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor, 12718 mBackgroundColor, nightMode); 12719 12720 // make sure every color has a valid value 12721 if (mPrimaryTextColor == COLOR_INVALID) { 12722 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor( 12723 ctx, mBackgroundColor, nightMode); 12724 } 12725 if (mSecondaryTextColor == COLOR_INVALID) { 12726 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor( 12727 ctx, mBackgroundColor, nightMode); 12728 } 12729 if (mPrimaryAccentColor == COLOR_INVALID) { 12730 mPrimaryAccentColor = mContrastColor; 12731 } 12732 if (mSecondaryAccentColor == COLOR_INVALID) { 12733 mSecondaryAccentColor = mContrastColor; 12734 } 12735 if (mTertiaryAccentColor == COLOR_INVALID) { 12736 mTertiaryAccentColor = mContrastColor; 12737 } 12738 if (mOnAccentTextColor == COLOR_INVALID) { 12739 mOnAccentTextColor = ColorUtils.setAlphaComponent( 12740 ContrastColorUtil.resolvePrimaryColor( 12741 ctx, mTertiaryAccentColor, nightMode), 0xFF); 12742 } 12743 if (mErrorColor == COLOR_INVALID) { 12744 mErrorColor = mPrimaryTextColor; 12745 } 12746 } 12747 // make sure every color has a valid value 12748 if (mProtectionColor == COLOR_INVALID) { 12749 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.8f); 12750 } 12751 } 12752 12753 /** calculates the contrast color for the non-colorized notifications */ calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)12754 private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor, 12755 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) { 12756 int color; 12757 if (rawColor == COLOR_DEFAULT) { 12758 color = accentColor; 12759 if (color == COLOR_INVALID) { 12760 color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode); 12761 } 12762 } else { 12763 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor, 12764 nightMode); 12765 } 12766 return flattenAlpha(color, backgroundColor); 12767 } 12768 12769 /** remove any alpha by manually blending it with the given background. */ flattenAlpha(@olorInt int color, @ColorInt int background)12770 private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) { 12771 return Color.alpha(color) == 0xff ? color 12772 : ContrastColorUtil.compositeColors(color, background); 12773 } 12774 12775 /** @return the notification's background color */ getBackgroundColor()12776 public @ColorInt int getBackgroundColor() { 12777 return mBackgroundColor; 12778 } 12779 12780 /** 12781 * @return the "surface protection" color from the theme, 12782 * or a variant of the normal background color when colorized. 12783 */ getProtectionColor()12784 public @ColorInt int getProtectionColor() { 12785 return mProtectionColor; 12786 } 12787 12788 /** @return the color for the most prominent text */ getPrimaryTextColor()12789 public @ColorInt int getPrimaryTextColor() { 12790 return mPrimaryTextColor; 12791 } 12792 12793 /** @return the color for less prominent text */ getSecondaryTextColor()12794 public @ColorInt int getSecondaryTextColor() { 12795 return mSecondaryTextColor; 12796 } 12797 12798 /** @return the theme's accent color for colored UI elements. */ getPrimaryAccentColor()12799 public @ColorInt int getPrimaryAccentColor() { 12800 return mPrimaryAccentColor; 12801 } 12802 12803 /** @return the theme's secondary accent color for colored UI elements. */ getSecondaryAccentColor()12804 public @ColorInt int getSecondaryAccentColor() { 12805 return mSecondaryAccentColor; 12806 } 12807 12808 /** @return the theme's tertiary accent color for colored UI elements. */ getTertiaryAccentColor()12809 public @ColorInt int getTertiaryAccentColor() { 12810 return mTertiaryAccentColor; 12811 } 12812 12813 /** @return the theme's text color to be used on the tertiary accent color. */ getOnAccentTextColor()12814 public @ColorInt int getOnAccentTextColor() { 12815 return mOnAccentTextColor; 12816 } 12817 12818 /** 12819 * @return the contrast-adjusted version of the color provided by the app, or the 12820 * primary text color when colorized. 12821 */ getContrastColor()12822 public @ColorInt int getContrastColor() { 12823 return mContrastColor; 12824 } 12825 12826 /** @return the theme's error color, or the primary text color when colorized. */ getErrorColor()12827 public @ColorInt int getErrorColor() { 12828 return mErrorColor; 12829 } 12830 12831 /** @return the alpha component of the current theme's control highlight color. */ getRippleAlpha()12832 public int getRippleAlpha() { 12833 return mRippleAlpha; 12834 } 12835 } 12836 } 12837