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.Flags.evenlyDividedCallStyleActionLayout; 21 import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION; 22 import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED; 23 import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON; 24 import static android.app.admin.DevicePolicyResources.UNDEFINED; 25 import static android.graphics.drawable.Icon.TYPE_URI; 26 import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP; 27 28 import static java.util.Objects.requireNonNull; 29 30 import android.annotation.ColorInt; 31 import android.annotation.ColorRes; 32 import android.annotation.DimenRes; 33 import android.annotation.Dimension; 34 import android.annotation.DrawableRes; 35 import android.annotation.FlaggedApi; 36 import android.annotation.IdRes; 37 import android.annotation.IntDef; 38 import android.annotation.NonNull; 39 import android.annotation.Nullable; 40 import android.annotation.RequiresPermission; 41 import android.annotation.SdkConstant; 42 import android.annotation.SdkConstant.SdkConstantType; 43 import android.annotation.StringRes; 44 import android.annotation.StyleableRes; 45 import android.annotation.SuppressLint; 46 import android.annotation.SystemApi; 47 import android.annotation.TestApi; 48 import android.app.admin.DevicePolicyManager; 49 import android.app.compat.CompatChanges; 50 import android.compat.annotation.ChangeId; 51 import android.compat.annotation.EnabledSince; 52 import android.compat.annotation.UnsupportedAppUsage; 53 import android.content.Context; 54 import android.content.Intent; 55 import android.content.LocusId; 56 import android.content.pm.ApplicationInfo; 57 import android.content.pm.PackageManager; 58 import android.content.pm.PackageManager.NameNotFoundException; 59 import android.content.pm.ShortcutInfo; 60 import android.content.res.ColorStateList; 61 import android.content.res.Configuration; 62 import android.content.res.Resources; 63 import android.content.res.TypedArray; 64 import android.graphics.Bitmap; 65 import android.graphics.Canvas; 66 import android.graphics.Color; 67 import android.graphics.PorterDuff; 68 import android.graphics.drawable.Drawable; 69 import android.graphics.drawable.Icon; 70 import android.media.AudioAttributes; 71 import android.media.AudioManager; 72 import android.media.PlayerBase; 73 import android.media.session.MediaSession; 74 import android.net.Uri; 75 import android.os.BadParcelableException; 76 import android.os.Build; 77 import android.os.Bundle; 78 import android.os.IBinder; 79 import android.os.Parcel; 80 import android.os.Parcelable; 81 import android.os.SystemClock; 82 import android.os.SystemProperties; 83 import android.os.Trace; 84 import android.os.UserHandle; 85 import android.os.UserManager; 86 import android.provider.Settings; 87 import android.text.BidiFormatter; 88 import android.text.SpannableStringBuilder; 89 import android.text.Spanned; 90 import android.text.TextUtils; 91 import android.text.style.AbsoluteSizeSpan; 92 import android.text.style.CharacterStyle; 93 import android.text.style.ForegroundColorSpan; 94 import android.text.style.RelativeSizeSpan; 95 import android.text.style.StrikethroughSpan; 96 import android.text.style.StyleSpan; 97 import android.text.style.TextAppearanceSpan; 98 import android.text.style.UnderlineSpan; 99 import android.util.ArraySet; 100 import android.util.Log; 101 import android.util.Pair; 102 import android.util.SparseArray; 103 import android.util.TypedValue; 104 import android.util.proto.ProtoOutputStream; 105 import android.view.ContextThemeWrapper; 106 import android.view.Gravity; 107 import android.view.View; 108 import android.view.contentcapture.ContentCaptureContext; 109 import android.widget.ProgressBar; 110 import android.widget.RemoteViews; 111 112 import com.android.internal.R; 113 import com.android.internal.annotations.VisibleForTesting; 114 import com.android.internal.graphics.ColorUtils; 115 import com.android.internal.util.ArrayUtils; 116 import com.android.internal.util.ContrastColorUtil; 117 import com.android.internal.util.NotificationBigTextNormalizer; 118 119 import java.lang.annotation.Retention; 120 import java.lang.annotation.RetentionPolicy; 121 import java.lang.reflect.Array; 122 import java.lang.reflect.Constructor; 123 import java.util.ArrayList; 124 import java.util.Arrays; 125 import java.util.Collections; 126 import java.util.List; 127 import java.util.Objects; 128 import java.util.Set; 129 import java.util.function.Consumer; 130 131 /** 132 * A class that represents how a persistent notification is to be presented to 133 * the user using the {@link android.app.NotificationManager}. 134 * 135 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 136 * easier to construct Notifications.</p> 137 * 138 * <div class="special reference"> 139 * <h3>Developer Guides</h3> 140 * <p>For a guide to creating notifications, read the 141 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 142 * developer guide.</p> 143 * </div> 144 */ 145 public class Notification implements Parcelable 146 { 147 private static final String TAG = "Notification"; 148 149 /** 150 * @hide 151 */ 152 @Retention(RetentionPolicy.SOURCE) 153 @IntDef({ 154 FOREGROUND_SERVICE_DEFAULT, 155 FOREGROUND_SERVICE_IMMEDIATE, 156 FOREGROUND_SERVICE_DEFERRED 157 }) 158 public @interface ServiceNotificationPolicy {}; 159 160 /** 161 * If the Notification associated with starting a foreground service has been 162 * built using setForegroundServiceBehavior() with this behavior, display of 163 * the notification will usually be suppressed for a short time to avoid visual 164 * disturbances to the user. 165 * @see Notification.Builder#setForegroundServiceBehavior(int) 166 * @see #FOREGROUND_SERVICE_IMMEDIATE 167 * @see #FOREGROUND_SERVICE_DEFERRED 168 */ 169 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0; 170 171 /** 172 * If the Notification associated with starting a foreground service has been 173 * built using setForegroundServiceBehavior() with this behavior, display of 174 * the notification will be immediate even if the default behavior would be 175 * to defer visibility for a short time. 176 * @see Notification.Builder#setForegroundServiceBehavior(int) 177 * @see #FOREGROUND_SERVICE_DEFAULT 178 * @see #FOREGROUND_SERVICE_DEFERRED 179 */ 180 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1; 181 182 /** 183 * If the Notification associated with starting a foreground service has been 184 * built using setForegroundServiceBehavior() with this behavior, display of 185 * the notification will usually be suppressed for a short time to avoid visual 186 * disturbances to the user. 187 * @see Notification.Builder#setForegroundServiceBehavior(int) 188 * @see #FOREGROUND_SERVICE_DEFAULT 189 * @see #FOREGROUND_SERVICE_IMMEDIATE 190 */ 191 public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2; 192 193 @ServiceNotificationPolicy 194 private int mFgsDeferBehavior; 195 196 /** 197 * An activity that provides a user interface for adjusting notification preferences for its 198 * containing application. 199 */ 200 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 201 public static final String INTENT_CATEGORY_NOTIFICATION_PREFERENCES 202 = "android.intent.category.NOTIFICATION_PREFERENCES"; 203 204 /** 205 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 206 * contain a {@link NotificationChannel#getId() channel id} that can be used to narrow down 207 * what settings should be shown in the target app. 208 */ 209 public static final String EXTRA_CHANNEL_ID = "android.intent.extra.CHANNEL_ID"; 210 211 /** 212 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 213 * contain a {@link NotificationChannelGroup#getId() group id} that can be used to narrow down 214 * what settings should be shown in the target app. 215 */ 216 public static final String EXTRA_CHANNEL_GROUP_ID = "android.intent.extra.CHANNEL_GROUP_ID"; 217 218 /** 219 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 220 * contain the tag provided to {@link NotificationManager#notify(String, int, Notification)} 221 * that can be used to narrow down what settings should be shown in the target app. 222 */ 223 public static final String EXTRA_NOTIFICATION_TAG = "android.intent.extra.NOTIFICATION_TAG"; 224 225 /** 226 * Optional extra for {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. If provided, will 227 * contain the id provided to {@link NotificationManager#notify(String, int, Notification)} 228 * that can be used to narrow down what settings should be shown in the target app. 229 */ 230 public static final String EXTRA_NOTIFICATION_ID = "android.intent.extra.NOTIFICATION_ID"; 231 232 /** 233 * Use all default values (where applicable). 234 */ 235 public static final int DEFAULT_ALL = ~0; 236 237 /** 238 * Use the default notification sound. This will ignore any given 239 * {@link #sound}. 240 * 241 * <p> 242 * A notification that is noisy is more likely to be presented as a heads-up notification. 243 * </p> 244 * 245 * @see #defaults 246 */ 247 248 public static final int DEFAULT_SOUND = 1; 249 250 /** 251 * Use the default notification vibrate. This will ignore any given 252 * {@link #vibrate}. Using phone vibration requires the 253 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 254 * 255 * <p> 256 * A notification that vibrates is more likely to be presented as a heads-up notification. 257 * </p> 258 * 259 * @see #defaults 260 */ 261 262 public static final int DEFAULT_VIBRATE = 2; 263 264 /** 265 * Use the default notification lights. This will ignore the 266 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 267 * {@link #ledOnMS}. 268 * 269 * @see #defaults 270 */ 271 272 public static final int DEFAULT_LIGHTS = 4; 273 274 /** 275 * Maximum length of CharSequences accepted by Builder and friends. 276 * 277 * <p> 278 * Avoids spamming the system with overly large strings such as full e-mails. 279 */ 280 private static final int MAX_CHARSEQUENCE_LENGTH = 1024; 281 282 /** 283 * Maximum entries of reply text that are accepted by Builder and friends. 284 */ 285 private static final int MAX_REPLY_HISTORY = 5; 286 287 /** 288 * Maximum aspect ratio of the large icon. 16:9 289 */ 290 private static final float MAX_LARGE_ICON_ASPECT_RATIO = 16f / 9f; 291 292 /** 293 * Maximum number of (generic) action buttons in a notification (contextual action buttons are 294 * handled separately). 295 * @hide 296 */ 297 public static final int MAX_ACTION_BUTTONS = 3; 298 299 /** 300 * If the notification contained an unsent draft for a RemoteInput when the user clicked on it, 301 * we're adding the draft as a String extra to the {@link #contentIntent} using this key. 302 * 303 * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually 304 * sends messages.</p> 305 */ 306 public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft"; 307 308 /** 309 * The call to WearableExtender#setBackground(Bitmap) will have no effect and the passed 310 * Bitmap will not be retained in memory. 311 */ 312 @ChangeId 313 @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) 314 @VisibleForTesting 315 static final long WEARABLE_EXTENDER_BACKGROUND_BLOCKED = 270551184L; 316 317 /** 318 * A timestamp related to this notification, in milliseconds since the epoch. 319 * 320 * Default value: {@link System#currentTimeMillis() Now}. 321 * 322 * Choose a timestamp that will be most relevant to the user. For most finite events, this 323 * corresponds to the time the event happened (or will happen, in the case of events that have 324 * yet to occur but about which the user is being informed). Indefinite events should be 325 * timestamped according to when the activity began. 326 * 327 * Some examples: 328 * 329 * <ul> 330 * <li>Notification of a new chat message should be stamped when the message was received.</li> 331 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 332 * <li>Notification of a completed file download should be stamped when the download finished.</li> 333 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 334 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 335 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 336 * </ul> 337 * 338 * For apps targeting {@link android.os.Build.VERSION_CODES#N} and above, this time is not shown 339 * anymore by default and must be opted into by using 340 * {@link android.app.Notification.Builder#setShowWhen(boolean)} 341 */ 342 public long when; 343 344 /** 345 * The creation time of the notification 346 * @hide 347 */ 348 public long creationTime; 349 350 /** 351 * The resource id of a drawable to use as the icon in the status bar. 352 * 353 * @deprecated Use {@link Builder#setSmallIcon(Icon)} instead. 354 */ 355 @Deprecated 356 @DrawableRes 357 public int icon; 358 359 /** 360 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 361 * leave it at its default value of 0. 362 * 363 * @see android.widget.ImageView#setImageLevel 364 * @see android.graphics.drawable.Drawable#setLevel 365 */ 366 public int iconLevel; 367 368 /** 369 * The number of events that this notification represents. For example, in a new mail 370 * notification, this could be the number of unread messages. 371 * 372 * The system may or may not use this field to modify the appearance of the notification. 373 * Starting with {@link android.os.Build.VERSION_CODES#O}, the number may be displayed as a 374 * badge icon in Launchers that support badging. 375 */ 376 public int number = 0; 377 378 /** 379 * The intent to execute when the expanded status entry is clicked. If 380 * this is an activity, it must include the 381 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 382 * that you take care of task management as described in the 383 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 384 * Stack</a> document. In particular, make sure to read the 385 * <a href="{@docRoot}training/notify-user/navigation">Start 386 * an Activity from a Notification</a> page for the correct ways to launch an application from a 387 * notification. 388 */ 389 public PendingIntent contentIntent; 390 391 /** 392 * The intent to execute when the notification is explicitly dismissed by the user, either with 393 * the "Clear All" button or by swiping it away individually. 394 * 395 * This probably shouldn't be launching an activity since several of those will be sent 396 * at the same time. 397 */ 398 public PendingIntent deleteIntent; 399 400 /** 401 * An intent to launch instead of posting the notification to the status bar. 402 * 403 * <p> 404 * The system UI may choose to display a heads-up notification, instead of 405 * launching this intent, while the user is using the device. 406 * </p> 407 * 408 * @see Notification.Builder#setFullScreenIntent 409 */ 410 public PendingIntent fullScreenIntent; 411 412 /** 413 * Text that summarizes this notification for accessibility services. 414 * 415 * As of the L release, this text is no longer shown on screen, but it is still useful to 416 * accessibility services (where it serves as an audible announcement of the notification's 417 * appearance). 418 * 419 * @see #tickerView 420 */ 421 public CharSequence tickerText; 422 423 /** 424 * Formerly, a view showing the {@link #tickerText}. 425 * 426 * No longer displayed in the status bar as of API 21. 427 */ 428 @Deprecated 429 public RemoteViews tickerView; 430 431 /** 432 * The view that will represent this notification in the notification list (which is pulled 433 * down from the status bar). 434 * 435 * As of N, this field may be null. The notification view is determined by the inputs 436 * to {@link Notification.Builder}; a custom RemoteViews can optionally be 437 * supplied with {@link Notification.Builder#setCustomContentView(RemoteViews)}. 438 */ 439 @Deprecated 440 public RemoteViews contentView; 441 442 /** 443 * A large-format version of {@link #contentView}, giving the Notification an 444 * opportunity to show more detail. The system UI may choose to show this 445 * instead of the normal content view at its discretion. 446 * 447 * As of N, this field may be null. The expanded notification view is determined by the 448 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 449 * supplied with {@link Notification.Builder#setCustomBigContentView(RemoteViews)}. 450 */ 451 @Deprecated 452 public RemoteViews bigContentView; 453 454 455 /** 456 * A medium-format version of {@link #contentView}, providing the Notification an 457 * opportunity to add action buttons to contentView. At its discretion, the system UI may 458 * choose to show this as a heads-up notification, which will pop up so the user can see 459 * it without leaving their current activity. 460 * 461 * As of N, this field may be null. The heads-up notification view is determined by the 462 * inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be 463 * supplied with {@link Notification.Builder#setCustomHeadsUpContentView(RemoteViews)}. 464 */ 465 @Deprecated 466 public RemoteViews headsUpContentView; 467 468 private boolean mUsesStandardHeader; 469 470 private static final ArraySet<Integer> STANDARD_LAYOUTS = new ArraySet<>(); 471 static { 472 STANDARD_LAYOUTS.add(R.layout.notification_template_material_base); 473 STANDARD_LAYOUTS.add(R.layout.notification_template_material_heads_up_base); 474 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_base); 475 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_picture); 476 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text); 477 STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox); 478 STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging); 479 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_messaging); 480 STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation); 481 STANDARD_LAYOUTS.add(R.layout.notification_template_material_media); 482 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media); 483 STANDARD_LAYOUTS.add(R.layout.notification_template_material_call); 484 STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_call); 485 STANDARD_LAYOUTS.add(R.layout.notification_template_header); 486 } 487 488 /** 489 * A large bitmap to be shown in the notification content area. 490 * 491 * @deprecated Use {@link Builder#setLargeIcon(Icon)} instead. 492 */ 493 @Deprecated 494 public Bitmap largeIcon; 495 496 /** 497 * The sound to play. 498 * 499 * <p> 500 * A notification that is noisy is more likely to be presented as a heads-up notification. 501 * </p> 502 * 503 * <p> 504 * To play the default notification sound, see {@link #defaults}. 505 * </p> 506 * @deprecated use {@link NotificationChannel#getSound()}. 507 */ 508 @Deprecated 509 public Uri sound; 510 511 /** 512 * Use this constant as the value for audioStreamType to request that 513 * the default stream type for notifications be used. Currently the 514 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 515 * 516 * @deprecated Use {@link NotificationChannel#getAudioAttributes()} instead. 517 */ 518 @Deprecated 519 public static final int STREAM_DEFAULT = -1; 520 521 /** 522 * The audio stream type to use when playing the sound. 523 * Should be one of the STREAM_ constants from 524 * {@link android.media.AudioManager}. 525 * 526 * @deprecated Use {@link #audioAttributes} instead. 527 */ 528 @Deprecated 529 public int audioStreamType = STREAM_DEFAULT; 530 531 /** 532 * The default value of {@link #audioAttributes}. 533 */ 534 public static final AudioAttributes AUDIO_ATTRIBUTES_DEFAULT = new AudioAttributes.Builder() 535 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 536 .setUsage(AudioAttributes.USAGE_NOTIFICATION) 537 .build(); 538 539 /** 540 * The {@link AudioAttributes audio attributes} to use when playing the sound. 541 * 542 * @deprecated use {@link NotificationChannel#getAudioAttributes()} instead. 543 */ 544 @Deprecated 545 public AudioAttributes audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 546 547 /** 548 * The pattern with which to vibrate. 549 * 550 * <p> 551 * To vibrate the default pattern, see {@link #defaults}. 552 * </p> 553 * 554 * @see android.os.Vibrator#vibrate(long[],int) 555 * @deprecated use {@link NotificationChannel#getVibrationPattern()}. 556 */ 557 @Deprecated 558 public long[] vibrate; 559 560 /** 561 * The color of the led. The hardware will do its best approximation. 562 * 563 * @see #FLAG_SHOW_LIGHTS 564 * @see #flags 565 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 566 */ 567 @ColorInt 568 @Deprecated 569 public int ledARGB; 570 571 /** 572 * The number of milliseconds for the LED to be on while it's flashing. 573 * The hardware will do its best approximation. 574 * 575 * @see #FLAG_SHOW_LIGHTS 576 * @see #flags 577 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 578 */ 579 @Deprecated 580 public int ledOnMS; 581 582 /** 583 * The number of milliseconds for the LED to be off while it's flashing. 584 * The hardware will do its best approximation. 585 * 586 * @see #FLAG_SHOW_LIGHTS 587 * @see #flags 588 * 589 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 590 */ 591 @Deprecated 592 public int ledOffMS; 593 594 /** 595 * Specifies which values should be taken from the defaults. 596 * <p> 597 * To set, OR the desired from {@link #DEFAULT_SOUND}, 598 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 599 * values, use {@link #DEFAULT_ALL}. 600 * </p> 601 * 602 * @deprecated use {@link NotificationChannel#getSound()} and 603 * {@link NotificationChannel#shouldShowLights()} and 604 * {@link NotificationChannel#shouldVibrate()}. 605 */ 606 @Deprecated 607 public int defaults; 608 609 /** 610 * Bit to be bitwise-ored into the {@link #flags} field that should be 611 * set if you want the LED on for this notification. 612 * <ul> 613 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 614 * or 0 for both ledOnMS and ledOffMS.</li> 615 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 616 * <li>To flash the LED, pass the number of milliseconds that it should 617 * be on and off to ledOnMS and ledOffMS.</li> 618 * </ul> 619 * <p> 620 * Since hardware varies, you are not guaranteed that any of the values 621 * you pass are honored exactly. Use the system defaults if possible 622 * because they will be set to values that work on any given hardware. 623 * <p> 624 * The alpha channel must be set for forward compatibility. 625 * 626 * @deprecated use {@link NotificationChannel#shouldShowLights()}. 627 */ 628 @Deprecated 629 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 630 631 /** 632 * Bit to be bitwise-ored into the {@link #flags} field that should be 633 * set if this notification is in reference to something that is ongoing, 634 * like a phone call. It should not be set if this notification is in 635 * reference to something that happened at a particular point in time, 636 * like a missed phone call. 637 */ 638 public static final int FLAG_ONGOING_EVENT = 0x00000002; 639 640 /** 641 * Bit to be bitwise-ored into the {@link #flags} field that if set, 642 * the audio will be repeated until the notification is 643 * cancelled or the notification window is opened. 644 */ 645 public static final int FLAG_INSISTENT = 0x00000004; 646 647 /** 648 * Bit to be bitwise-ored into the {@link #flags} field that should be 649 * set if you would only like the sound, vibrate and ticker to be played 650 * if the notification was not already showing. 651 * 652 * Note that using this flag will stop any ongoing alerting behaviour such 653 * as sound, vibration or blinking notification LED. 654 */ 655 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 656 657 /** 658 * Bit to be bitwise-ored into the {@link #flags} field that should be 659 * set if the notification should be canceled when it is clicked by the 660 * user. 661 */ 662 public static final int FLAG_AUTO_CANCEL = 0x00000010; 663 664 /** 665 * Bit to be bitwise-ored into the {@link #flags} field that should be 666 * set if the notification should not be canceled when the user clicks 667 * the Clear all button. 668 */ 669 public static final int FLAG_NO_CLEAR = 0x00000020; 670 671 /** 672 * Bit to be bitwise-ored into the {@link #flags} field that should be 673 * set if this notification represents a currently running service. This 674 * will normally be set for you by {@link Service#startForeground}. 675 */ 676 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 677 678 /** 679 * Obsolete flag indicating high-priority notifications; use the priority field instead. 680 * 681 * @deprecated Use {@link #priority} with a positive value. 682 */ 683 @Deprecated 684 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 685 686 /** 687 * Bit to be bitswise-ored into the {@link #flags} field that should be 688 * set if this notification is relevant to the current device only 689 * and it is not recommended that it bridge to other devices. 690 */ 691 public static final int FLAG_LOCAL_ONLY = 0x00000100; 692 693 /** 694 * Bit to be bitswise-ored into the {@link #flags} field that should be 695 * set if this notification is the group summary for a group of notifications. 696 * Grouped notifications may display in a cluster or stack on devices which 697 * support such rendering. Requires a group key also be set using {@link Builder#setGroup}. 698 */ 699 public static final int FLAG_GROUP_SUMMARY = 0x00000200; 700 701 /** 702 * Bit to be bitswise-ored into the {@link #flags} field that should be 703 * set if this notification is the group summary for an auto-group of notifications. 704 * 705 * @hide 706 */ 707 @SystemApi 708 public static final int FLAG_AUTOGROUP_SUMMARY = 0x00000400; 709 710 /** 711 * @hide 712 */ 713 public static final int FLAG_CAN_COLORIZE = 0x00000800; 714 715 /** 716 * Bit to be bitswised-ored into the {@link #flags} field that should be 717 * set by the system if this notification is showing as a bubble. 718 * 719 * Applications cannot set this flag directly; they should instead call 720 * {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)} to 721 * request that a notification be displayed as a bubble, and then check 722 * this flag to see whether that request was honored by the system. 723 */ 724 public static final int FLAG_BUBBLE = 0x00001000; 725 726 /** 727 * Bit to be bitswised-ored into the {@link #flags} field that should be 728 * set by the system if this notification is not dismissible. 729 * 730 * This flag is for internal use only; applications cannot set this flag directly. 731 * @hide 732 */ 733 public static final int FLAG_NO_DISMISS = 0x00002000; 734 735 /** 736 * Bit to be bitwise-ORed into the {@link #flags} field that should be 737 * set by the system if the app that sent this notification does not have the permission to send 738 * full screen intents. 739 * 740 * This flag is for internal use only; applications cannot set this flag directly. 741 * @hide 742 */ 743 public static final int FLAG_FSI_REQUESTED_BUT_DENIED = 0x00004000; 744 745 /** 746 * Bit to be bitwise-ored into the {@link #flags} field that should be 747 * set if this notification represents a currently running user-initiated job. 748 * 749 * This flag is for internal use only; applications cannot set this flag directly. 750 * @hide 751 */ 752 @TestApi 753 public static final int FLAG_USER_INITIATED_JOB = 0x00008000; 754 755 /** 756 * Bit to be bitwise-ored into the {@link #flags} field that should be 757 * set if this notification has been lifetime extended due to a direct reply. 758 * 759 * This flag is for internal use only; applications cannot set this flag directly. 760 * @hide 761 */ 762 @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) 763 public static final int FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY = 0x00010000; 764 765 private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList( 766 BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, 767 DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, 768 MessagingStyle.class, CallStyle.class); 769 770 /** @hide */ 771 @IntDef(flag = true, prefix = {"FLAG_"}, value = { 772 FLAG_SHOW_LIGHTS, 773 FLAG_ONGOING_EVENT, 774 FLAG_INSISTENT, 775 FLAG_ONLY_ALERT_ONCE, 776 FLAG_AUTO_CANCEL, 777 FLAG_NO_CLEAR, 778 FLAG_FOREGROUND_SERVICE, 779 FLAG_HIGH_PRIORITY, 780 FLAG_LOCAL_ONLY, 781 FLAG_GROUP_SUMMARY, 782 FLAG_AUTOGROUP_SUMMARY, 783 FLAG_CAN_COLORIZE, 784 FLAG_BUBBLE, 785 FLAG_NO_DISMISS, 786 FLAG_FSI_REQUESTED_BUT_DENIED, 787 FLAG_USER_INITIATED_JOB 788 }) 789 @Retention(RetentionPolicy.SOURCE) 790 public @interface NotificationFlags{}; 791 792 public int flags; 793 794 /** @hide */ 795 @IntDef(prefix = { "PRIORITY_" }, value = { 796 PRIORITY_DEFAULT, 797 PRIORITY_LOW, 798 PRIORITY_MIN, 799 PRIORITY_HIGH, 800 PRIORITY_MAX 801 }) 802 @Retention(RetentionPolicy.SOURCE) 803 public @interface Priority {} 804 805 /** 806 * Default notification {@link #priority}. If your application does not prioritize its own 807 * notifications, use this value for all notifications. 808 * 809 * @deprecated use {@link NotificationManager#IMPORTANCE_DEFAULT} instead. 810 */ 811 @Deprecated 812 public static final int PRIORITY_DEFAULT = 0; 813 814 /** 815 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 816 * items smaller, or at a different position in the list, compared with your app's 817 * {@link #PRIORITY_DEFAULT} items. 818 * 819 * @deprecated use {@link NotificationManager#IMPORTANCE_LOW} instead. 820 */ 821 @Deprecated 822 public static final int PRIORITY_LOW = -1; 823 824 /** 825 * Lowest {@link #priority}; these items might not be shown to the user except under special 826 * circumstances, such as detailed notification logs. 827 * 828 * @deprecated use {@link NotificationManager#IMPORTANCE_MIN} instead. 829 */ 830 @Deprecated 831 public static final int PRIORITY_MIN = -2; 832 833 /** 834 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 835 * show these items larger, or at a different position in notification lists, compared with 836 * your app's {@link #PRIORITY_DEFAULT} items. 837 * 838 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 839 */ 840 @Deprecated 841 public static final int PRIORITY_HIGH = 1; 842 843 /** 844 * Highest {@link #priority}, for your application's most important items that require the 845 * user's prompt attention or input. 846 * 847 * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead. 848 */ 849 @Deprecated 850 public static final int PRIORITY_MAX = 2; 851 852 /** 853 * Relative priority for this notification. 854 * 855 * Priority is an indication of how much of the user's valuable attention should be consumed by 856 * this notification. Low-priority notifications may be hidden from the user in certain 857 * situations, while the user might be interrupted for a higher-priority notification. The 858 * system will make a determination about how to interpret this priority when presenting 859 * the notification. 860 * 861 * <p> 862 * A notification that is at least {@link #PRIORITY_HIGH} is more likely to be presented 863 * as a heads-up notification. 864 * </p> 865 * 866 * @deprecated use {@link NotificationChannel#getImportance()} instead. 867 */ 868 @Priority 869 @Deprecated 870 public int priority; 871 872 /** 873 * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) 874 * to be applied by the standard Style templates when presenting this notification. 875 * 876 * The current template design constructs a colorful header image by overlaying the 877 * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are 878 * ignored. 879 */ 880 @ColorInt 881 public int color = COLOR_DEFAULT; 882 883 /** 884 * Special value of {@link #color} telling the system not to decorate this notification with 885 * any special color but instead use default colors when presenting this notification. 886 */ 887 @ColorInt 888 public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT 889 890 /** 891 * Special value of {@link #color} used as a place holder for an invalid color. 892 * @hide 893 */ 894 @ColorInt 895 public static final int COLOR_INVALID = 1; 896 897 /** 898 * Sphere of visibility of this notification, which affects how and when the SystemUI reveals 899 * the notification's presence and contents in untrusted situations (namely, on the secure 900 * lockscreen and during screen sharing). 901 * 902 * The default level, {@link #VISIBILITY_PRIVATE}, behaves exactly as notifications have always 903 * done on Android: The notification's {@link #icon} and {@link #tickerText} (if available) are 904 * shown in all situations, but the contents are only available if the device is unlocked for 905 * the appropriate user and there is no active screen sharing session. 906 * 907 * A more permissive policy can be expressed by {@link #VISIBILITY_PUBLIC}; such a notification 908 * can be read even in an "insecure" context (that is, above a secure lockscreen or while 909 * screen sharing with a remote viewer). 910 * To modify the public version of this notification—for example, to redact some portions—see 911 * {@link Builder#setPublicVersion(Notification)}. 912 * 913 * Finally, a notification can be made {@link #VISIBILITY_SECRET}, which will suppress its icon 914 * and ticker until the user has bypassed the lockscreen. 915 */ 916 public @Visibility int visibility; 917 918 /** @hide */ 919 @IntDef(prefix = { "VISIBILITY_" }, value = { 920 VISIBILITY_PUBLIC, 921 VISIBILITY_PRIVATE, 922 VISIBILITY_SECRET, 923 }) 924 @Retention(RetentionPolicy.SOURCE) 925 public @interface Visibility {} 926 927 /** 928 * Notification visibility: Show this notification in its entirety on all lockscreens and while 929 * screen sharing. 930 * 931 * {@see #visibility} 932 */ 933 public static final int VISIBILITY_PUBLIC = 1; 934 935 /** 936 * Notification visibility: Show this notification on all lockscreens, but conceal sensitive or 937 * private information on secure lockscreens. Conceal sensitive or private information while 938 * screen sharing. 939 * 940 * {@see #visibility} 941 */ 942 public static final int VISIBILITY_PRIVATE = 0; 943 944 /** 945 * Notification visibility: Do not reveal any part of this notification on a secure lockscreen 946 * or while screen sharing. 947 * 948 * {@see #visibility} 949 */ 950 public static final int VISIBILITY_SECRET = -1; 951 952 /** 953 * @hide 954 */ 955 @IntDef(prefix = "VISIBILITY_", value = { 956 VISIBILITY_PUBLIC, 957 VISIBILITY_PRIVATE, 958 VISIBILITY_SECRET, 959 NotificationManager.VISIBILITY_NO_OVERRIDE 960 }) 961 @Retention(RetentionPolicy.SOURCE) 962 public @interface NotificationVisibilityOverride{}; 963 964 /** 965 * Notification category: incoming call (voice or video) or similar synchronous communication request. 966 */ 967 public static final String CATEGORY_CALL = "call"; 968 969 /** 970 * Notification category: map turn-by-turn navigation. 971 */ 972 public static final String CATEGORY_NAVIGATION = "navigation"; 973 974 /** 975 * Notification category: incoming direct message (SMS, instant message, etc.). 976 */ 977 public static final String CATEGORY_MESSAGE = "msg"; 978 979 /** 980 * Notification category: asynchronous bulk message (email). 981 */ 982 public static final String CATEGORY_EMAIL = "email"; 983 984 /** 985 * Notification category: calendar event. 986 */ 987 public static final String CATEGORY_EVENT = "event"; 988 989 /** 990 * Notification category: promotion or advertisement. 991 */ 992 public static final String CATEGORY_PROMO = "promo"; 993 994 /** 995 * Notification category: alarm or timer. 996 */ 997 public static final String CATEGORY_ALARM = "alarm"; 998 999 /** 1000 * Notification category: progress of a long-running background operation. 1001 */ 1002 public static final String CATEGORY_PROGRESS = "progress"; 1003 1004 /** 1005 * Notification category: social network or sharing update. 1006 */ 1007 public static final String CATEGORY_SOCIAL = "social"; 1008 1009 /** 1010 * Notification category: error in background operation or authentication status. 1011 */ 1012 public static final String CATEGORY_ERROR = "err"; 1013 1014 /** 1015 * Notification category: media transport control for playback. 1016 */ 1017 public static final String CATEGORY_TRANSPORT = "transport"; 1018 1019 /** 1020 * Notification category: system or device status update. Reserved for system use. 1021 */ 1022 public static final String CATEGORY_SYSTEM = "sys"; 1023 1024 /** 1025 * Notification category: indication of running background service. 1026 */ 1027 public static final String CATEGORY_SERVICE = "service"; 1028 1029 /** 1030 * Notification category: a specific, timely recommendation for a single thing. 1031 * For example, a news app might want to recommend a news story it believes the user will 1032 * want to read next. 1033 */ 1034 public static final String CATEGORY_RECOMMENDATION = "recommendation"; 1035 1036 /** 1037 * Notification category: ongoing information about device or contextual status. 1038 */ 1039 public static final String CATEGORY_STATUS = "status"; 1040 1041 /** 1042 * Notification category: user-scheduled reminder. 1043 */ 1044 public static final String CATEGORY_REMINDER = "reminder"; 1045 1046 /** 1047 * Notification category: extreme car emergencies. 1048 * @hide 1049 */ 1050 @SystemApi 1051 public static final String CATEGORY_CAR_EMERGENCY = "car_emergency"; 1052 1053 /** 1054 * Notification category: car warnings. 1055 * @hide 1056 */ 1057 @SystemApi 1058 public static final String CATEGORY_CAR_WARNING = "car_warning"; 1059 1060 /** 1061 * Notification category: general car system information. 1062 * @hide 1063 */ 1064 @SystemApi 1065 public static final String CATEGORY_CAR_INFORMATION = "car_information"; 1066 1067 /** 1068 * Notification category: tracking a user's workout. 1069 */ 1070 public static final String CATEGORY_WORKOUT = "workout"; 1071 1072 /** 1073 * Notification category: temporarily sharing location. 1074 */ 1075 public static final String CATEGORY_LOCATION_SHARING = "location_sharing"; 1076 1077 /** 1078 * Notification category: running stopwatch. 1079 */ 1080 public static final String CATEGORY_STOPWATCH = "stopwatch"; 1081 1082 /** 1083 * Notification category: missed call. 1084 */ 1085 public static final String CATEGORY_MISSED_CALL = "missed_call"; 1086 1087 /** 1088 * Notification category: voicemail. 1089 */ 1090 @FlaggedApi(Flags.FLAG_CATEGORY_VOICEMAIL) 1091 public static final String CATEGORY_VOICEMAIL = "voicemail"; 1092 1093 /** 1094 * One of the predefined notification categories (see the <code>CATEGORY_*</code> constants) 1095 * that best describes this Notification. May be used by the system for ranking and filtering. 1096 */ 1097 public String category; 1098 1099 @UnsupportedAppUsage 1100 private String mGroupKey; 1101 1102 /** 1103 * Get the key used to group this notification into a cluster or stack 1104 * with other notifications on devices which support such rendering. 1105 */ getGroup()1106 public String getGroup() { 1107 return mGroupKey; 1108 } 1109 1110 private String mSortKey; 1111 1112 /** 1113 * Get a sort key that orders this notification among other notifications from the 1114 * same package. This can be useful if an external sort was already applied and an app 1115 * would like to preserve this. Notifications will be sorted lexicographically using this 1116 * value, although providing different priorities in addition to providing sort key may 1117 * cause this value to be ignored. 1118 * 1119 * <p>This sort key can also be used to order members of a notification group. See 1120 * {@link Builder#setGroup}. 1121 * 1122 * @see String#compareTo(String) 1123 */ getSortKey()1124 public String getSortKey() { 1125 return mSortKey; 1126 } 1127 1128 /** 1129 * Additional semantic data to be carried around with this Notification. 1130 * <p> 1131 * The extras keys defined here are intended to capture the original inputs to {@link Builder} 1132 * APIs, and are intended to be used by 1133 * {@link android.service.notification.NotificationListenerService} implementations to extract 1134 * detailed information from notification objects. 1135 */ 1136 public Bundle extras = new Bundle(); 1137 1138 /** 1139 * All pending intents in the notification as the system needs to be able to access them but 1140 * touching the extras bundle in the system process is not safe because the bundle may contain 1141 * custom parcelable objects. 1142 * 1143 * @hide 1144 */ 1145 @UnsupportedAppUsage 1146 public ArraySet<PendingIntent> allPendingIntents; 1147 1148 /** 1149 * Token identifying the notification that is applying doze/bgcheck allowlisting to the 1150 * pending intents inside of it, so only those will get the behavior. 1151 * 1152 * @hide 1153 */ 1154 private IBinder mAllowlistToken; 1155 1156 /** 1157 * Must be set by a process to start associating tokens with Notification objects 1158 * coming in to it. This is set by NotificationManagerService. 1159 * 1160 * @hide 1161 */ 1162 static public IBinder processAllowlistToken; 1163 1164 /** 1165 * {@link #extras} key: this is the title of the notification, 1166 * as supplied to {@link Builder#setContentTitle(CharSequence)}. 1167 */ 1168 public static final String EXTRA_TITLE = "android.title"; 1169 1170 /** 1171 * {@link #extras} key: this is the title of the notification when shown in expanded form, 1172 * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}. 1173 */ 1174 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 1175 1176 /** 1177 * {@link #extras} key: this is the main text payload, as supplied to 1178 * {@link Builder#setContentText(CharSequence)}. 1179 */ 1180 public static final String EXTRA_TEXT = "android.text"; 1181 1182 /** 1183 * {@link #extras} key: this is a third line of text, as supplied to 1184 * {@link Builder#setSubText(CharSequence)}. 1185 */ 1186 public static final String EXTRA_SUB_TEXT = "android.subText"; 1187 1188 /** 1189 * {@link #extras} key: this is the remote input history, as supplied to 1190 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1191 * 1192 * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])} 1193 * with the most recent inputs that have been sent through a {@link RemoteInput} of this 1194 * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat 1195 * notifications once the other party has responded). 1196 * 1197 * The extra with this key is of type CharSequence[] and contains the most recent entry at 1198 * the 0 index, the second most recent at the 1 index, etc. 1199 * 1200 * @see Builder#setRemoteInputHistory(CharSequence[]) 1201 */ 1202 public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory"; 1203 1204 1205 /** 1206 * {@link #extras} key: this is a remote input history which can include media messages 1207 * in addition to text, as supplied to 1208 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} or 1209 * {@link Builder#setRemoteInputHistory(CharSequence[])}. 1210 * 1211 * SystemUI can populate this through 1212 * {@link Builder#setRemoteInputHistory(RemoteInputHistoryItem[])} with the most recent inputs 1213 * that have been sent through a {@link RemoteInput} of this Notification. These items can 1214 * represent either media content (specified by a URI and a MIME type) or a text message 1215 * (described by a CharSequence). 1216 * 1217 * To maintain compatibility, this can also be set by apps with 1218 * {@link Builder#setRemoteInputHistory(CharSequence[])}, which will create a 1219 * {@link RemoteInputHistoryItem} for each of the provided text-only messages. 1220 * 1221 * The extra with this key is of type {@link RemoteInputHistoryItem[]} and contains the most 1222 * recent entry at the 0 index, the second most recent at the 1 index, etc. 1223 * 1224 * @see Builder#setRemoteInputHistory(RemoteInputHistoryItem[]) 1225 * @hide 1226 */ 1227 public static final String EXTRA_REMOTE_INPUT_HISTORY_ITEMS = "android.remoteInputHistoryItems"; 1228 1229 /** 1230 * {@link #extras} key: boolean as supplied to 1231 * {@link Builder#setShowRemoteInputSpinner(boolean)}. 1232 * 1233 * If set to true, then the view displaying the remote input history from 1234 * {@link Builder#setRemoteInputHistory(CharSequence[])} will have a progress spinner. 1235 * 1236 * @see Builder#setShowRemoteInputSpinner(boolean) 1237 * @hide 1238 */ 1239 public static final String EXTRA_SHOW_REMOTE_INPUT_SPINNER = "android.remoteInputSpinner"; 1240 1241 /** 1242 * {@link #extras} key: boolean as supplied to 1243 * {@link Builder#setHideSmartReplies(boolean)}. 1244 * 1245 * If set to true, then any smart reply buttons will be hidden. 1246 * 1247 * @see Builder#setHideSmartReplies(boolean) 1248 * @hide 1249 */ 1250 public static final String EXTRA_HIDE_SMART_REPLIES = "android.hideSmartReplies"; 1251 1252 /** 1253 * {@link #extras} key: this is a small piece of additional text as supplied to 1254 * {@link Builder#setContentInfo(CharSequence)}. 1255 */ 1256 public static final String EXTRA_INFO_TEXT = "android.infoText"; 1257 1258 /** 1259 * {@link #extras} key: this is a line of summary information intended to be shown 1260 * alongside expanded notifications, as supplied to (e.g.) 1261 * {@link BigTextStyle#setSummaryText(CharSequence)}. 1262 */ 1263 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 1264 1265 /** 1266 * {@link #extras} key: this is the longer text shown in the big form of a 1267 * {@link BigTextStyle} notification, as supplied to 1268 * {@link BigTextStyle#bigText(CharSequence)}. 1269 */ 1270 public static final String EXTRA_BIG_TEXT = "android.bigText"; 1271 1272 /** 1273 * {@link #extras} key: this is the resource ID of the notification's main small icon, as 1274 * supplied to {@link Builder#setSmallIcon(int)}. 1275 * 1276 * @deprecated Use {@link #getSmallIcon()}, which supports a wider variety of icon sources. 1277 */ 1278 @Deprecated 1279 public static final String EXTRA_SMALL_ICON = "android.icon"; 1280 1281 /** 1282 * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the 1283 * notification payload, as 1284 * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}. 1285 * 1286 * @deprecated Use {@link #getLargeIcon()}, which supports a wider variety of icon sources. 1287 */ 1288 @Deprecated 1289 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 1290 1291 /** 1292 * {@link #extras} key: this is a bitmap to be used instead of the one from 1293 * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is 1294 * shown in its expanded form, as supplied to 1295 * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}. 1296 */ 1297 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 1298 1299 /** 1300 * {@link #extras} key: this is the progress value supplied to 1301 * {@link Builder#setProgress(int, int, boolean)}. 1302 */ 1303 public static final String EXTRA_PROGRESS = "android.progress"; 1304 1305 /** 1306 * {@link #extras} key: this is the maximum value supplied to 1307 * {@link Builder#setProgress(int, int, boolean)}. 1308 */ 1309 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 1310 1311 /** 1312 * {@link #extras} key: whether the progress bar is indeterminate, supplied to 1313 * {@link Builder#setProgress(int, int, boolean)}. 1314 */ 1315 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 1316 1317 /** 1318 * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically 1319 * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to 1320 * {@link Builder#setUsesChronometer(boolean)}. 1321 */ 1322 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 1323 1324 /** 1325 * {@link #extras} key: whether the chronometer set on the notification should count down 1326 * instead of counting up. Is only relevant if key {@link #EXTRA_SHOW_CHRONOMETER} is present. 1327 * This extra is a boolean. The default is false. 1328 */ 1329 public static final String EXTRA_CHRONOMETER_COUNT_DOWN = "android.chronometerCountDown"; 1330 1331 /** 1332 * {@link #extras} key: whether {@link #when} should be shown, 1333 * as supplied to {@link Builder#setShowWhen(boolean)}. 1334 */ 1335 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 1336 1337 /** 1338 * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded 1339 * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}. 1340 */ 1341 public static final String EXTRA_PICTURE = "android.picture"; 1342 1343 /** 1344 * {@link #extras} key: this is an {@link Icon} of an image to be 1345 * shown in {@link BigPictureStyle} expanded notifications, supplied to 1346 * {@link BigPictureStyle#bigPicture(Icon)}. 1347 */ 1348 public static final String EXTRA_PICTURE_ICON = "android.pictureIcon"; 1349 1350 /** 1351 * {@link #extras} key: this is a content description of the big picture supplied from 1352 * {@link BigPictureStyle#bigPicture(Bitmap)}, supplied to 1353 * {@link BigPictureStyle#setContentDescription(CharSequence)}. 1354 */ 1355 public static final String EXTRA_PICTURE_CONTENT_DESCRIPTION = 1356 "android.pictureContentDescription"; 1357 1358 /** 1359 * {@link #extras} key: this is a boolean to indicate that the 1360 * {@link BigPictureStyle#bigPicture(Bitmap) big picture} is to be shown in the collapsed state 1361 * of a {@link BigPictureStyle} notification. This will replace a 1362 * {@link Builder#setLargeIcon(Icon) large icon} in that state if one was provided. 1363 */ 1364 public static final String EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED = 1365 "android.showBigPictureWhenCollapsed"; 1366 1367 /** 1368 * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded 1369 * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}. 1370 */ 1371 public static final String EXTRA_TEXT_LINES = "android.textLines"; 1372 1373 /** 1374 * {@link #extras} key: A string representing the name of the specific 1375 * {@link android.app.Notification.Style} used to create this notification. 1376 */ 1377 public static final String EXTRA_TEMPLATE = "android.template"; 1378 1379 /** 1380 * {@link #extras} key: A String array containing the people that this notification relates to, 1381 * each of which was supplied to {@link Builder#addPerson(String)}. 1382 * 1383 * @deprecated the actual objects are now in {@link #EXTRA_PEOPLE_LIST} 1384 */ 1385 public static final String EXTRA_PEOPLE = "android.people"; 1386 1387 /** 1388 * {@link #extras} key: An arrayList of {@link Person} objects containing the people that 1389 * this notification relates to. 1390 */ 1391 public static final String EXTRA_PEOPLE_LIST = "android.people.list"; 1392 1393 /** 1394 * Allow certain system-generated notifications to appear before the device is provisioned. 1395 * Only available to notifications coming from the android package. 1396 * @hide 1397 */ 1398 @SystemApi 1399 @RequiresPermission(android.Manifest.permission.NOTIFICATION_DURING_SETUP) 1400 public static final String EXTRA_ALLOW_DURING_SETUP = "android.allowDuringSetup"; 1401 1402 /** 1403 * {@link #extras} key: 1404 * flat {@link String} representation of a {@link android.content.ContentUris content URI} 1405 * pointing to an image that can be displayed in the background when the notification is 1406 * selected. Used on television platforms. The URI must point to an image stream suitable for 1407 * passing into {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) 1408 * BitmapFactory.decodeStream}; all other content types will be ignored. 1409 */ 1410 public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; 1411 1412 /** 1413 * {@link #extras} key: A 1414 * {@link android.media.session.MediaSession.Token} associated with a 1415 * {@link android.app.Notification.MediaStyle} notification. 1416 */ 1417 public static final String EXTRA_MEDIA_SESSION = "android.mediaSession"; 1418 1419 /** 1420 * {@link #extras} key: A {@code CharSequence} name of a remote device used for a media session 1421 * associated with a {@link Notification.MediaStyle} notification. This will show in the media 1422 * controls output switcher instead of the local device name. 1423 * @hide 1424 */ 1425 @TestApi 1426 public static final String EXTRA_MEDIA_REMOTE_DEVICE = "android.mediaRemoteDevice"; 1427 1428 /** 1429 * {@link #extras} key: A {@code int} resource ID for an icon that should show in the output 1430 * switcher of the media controls for a {@link Notification.MediaStyle} notification. 1431 * @hide 1432 */ 1433 @TestApi 1434 public static final String EXTRA_MEDIA_REMOTE_ICON = "android.mediaRemoteIcon"; 1435 1436 /** 1437 * {@link #extras} key: A {@code PendingIntent} that will replace the default action for the 1438 * media controls output switcher chip, associated with a {@link Notification.MediaStyle} 1439 * notification. This should launch an activity. 1440 * @hide 1441 */ 1442 @TestApi 1443 public static final String EXTRA_MEDIA_REMOTE_INTENT = "android.mediaRemoteIntent"; 1444 1445 /** 1446 * {@link #extras} key: the indices of actions to be shown in the compact view, 1447 * as supplied to (e.g.) {@link MediaStyle#setShowActionsInCompactView(int...)}. 1448 */ 1449 public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions"; 1450 1451 /** 1452 * {@link #extras} key: the username to be displayed for all messages sent by the user including 1453 * direct replies 1454 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1455 * {@link CharSequence} 1456 * 1457 * @deprecated use {@link #EXTRA_MESSAGING_PERSON} 1458 */ 1459 public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName"; 1460 1461 /** 1462 * {@link #extras} key: the person to be displayed for all messages sent by the user including 1463 * direct replies 1464 * {@link android.app.Notification.MessagingStyle} notification. This extra is a 1465 * {@link Person} 1466 */ 1467 public static final String EXTRA_MESSAGING_PERSON = "android.messagingUser"; 1468 1469 /** 1470 * {@link #extras} key: a {@link CharSequence} to be displayed as the title to a conversation 1471 * represented by a {@link android.app.Notification.MessagingStyle} 1472 */ 1473 public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle"; 1474 1475 /** @hide */ 1476 public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon"; 1477 1478 /** @hide */ 1479 public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT = 1480 "android.conversationUnreadMessageCount"; 1481 1482 /** 1483 * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message} 1484 * bundles provided by a 1485 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1486 * array of bundles. 1487 */ 1488 public static final String EXTRA_MESSAGES = "android.messages"; 1489 1490 /** 1491 * {@link #extras} key: an array of 1492 * {@link android.app.Notification.MessagingStyle#addHistoricMessage historic} 1493 * {@link android.app.Notification.MessagingStyle.Message} bundles provided by a 1494 * {@link android.app.Notification.MessagingStyle} notification. This extra is a parcelable 1495 * array of bundles. 1496 */ 1497 public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic"; 1498 1499 /** 1500 * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification 1501 * represents a group conversation. 1502 */ 1503 public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation"; 1504 1505 /** 1506 * {@link #extras} key: the type of call represented by the 1507 * {@link android.app.Notification.CallStyle} notification. This extra is an int. 1508 */ 1509 public static final String EXTRA_CALL_TYPE = "android.callType"; 1510 1511 /** 1512 * {@link #extras} key: whether the {@link android.app.Notification.CallStyle} notification 1513 * is for a call that will activate video when answered. This extra is a boolean. 1514 */ 1515 public static final String EXTRA_CALL_IS_VIDEO = "android.callIsVideo"; 1516 1517 /** 1518 * {@link #extras} key: the person to be displayed as calling for the 1519 * {@link android.app.Notification.CallStyle} notification. This extra is a {@link Person}. 1520 */ 1521 public static final String EXTRA_CALL_PERSON = "android.callPerson"; 1522 1523 /** 1524 * {@link #extras} key: the icon to be displayed as a verification status of the caller on a 1525 * {@link android.app.Notification.CallStyle} notification. This extra is an {@link Icon}. 1526 */ 1527 public static final String EXTRA_VERIFICATION_ICON = "android.verificationIcon"; 1528 1529 /** 1530 * {@link #extras} key: the text to be displayed as a verification status of the caller on a 1531 * {@link android.app.Notification.CallStyle} notification. This extra is a 1532 * {@link CharSequence}. 1533 */ 1534 public static final String EXTRA_VERIFICATION_TEXT = "android.verificationText"; 1535 1536 /** 1537 * {@link #extras} key: the intent to be sent when the users answers a 1538 * {@link android.app.Notification.CallStyle} notification. This extra is a 1539 * {@link PendingIntent}. 1540 */ 1541 public static final String EXTRA_ANSWER_INTENT = "android.answerIntent"; 1542 1543 /** 1544 * {@link #extras} key: the intent to be sent when the users declines a 1545 * {@link android.app.Notification.CallStyle} notification. This extra is a 1546 * {@link PendingIntent}. 1547 */ 1548 public static final String EXTRA_DECLINE_INTENT = "android.declineIntent"; 1549 1550 /** 1551 * {@link #extras} key: the intent to be sent when the users hangs up a 1552 * {@link android.app.Notification.CallStyle} notification. This extra is a 1553 * {@link PendingIntent}. 1554 */ 1555 public static final String EXTRA_HANG_UP_INTENT = "android.hangUpIntent"; 1556 1557 /** 1558 * {@link #extras} key: the color used as a hint for the Answer action button of a 1559 * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}. 1560 */ 1561 public static final String EXTRA_ANSWER_COLOR = "android.answerColor"; 1562 1563 /** 1564 * {@link #extras} key: the color used as a hint for the Decline or Hang Up action button of a 1565 * {@link android.app.Notification.CallStyle} notification. This extra is a {@code ColorInt}. 1566 */ 1567 public static final String EXTRA_DECLINE_COLOR = "android.declineColor"; 1568 1569 /** 1570 * {@link #extras} key: whether the notification should be colorized as 1571 * supplied to {@link Builder#setColorized(boolean)}. 1572 */ 1573 public static final String EXTRA_COLORIZED = "android.colorized"; 1574 1575 /** 1576 * @hide 1577 */ 1578 public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo"; 1579 1580 /** 1581 * @hide 1582 */ 1583 public static final String EXTRA_CONTAINS_CUSTOM_VIEW = "android.contains.customView"; 1584 1585 /** 1586 * @hide 1587 */ 1588 public static final String EXTRA_REDUCED_IMAGES = "android.reduced.images"; 1589 1590 /** 1591 * {@link #extras} key: the audio contents of this notification. 1592 * 1593 * This is for use when rendering the notification on an audio-focused interface; 1594 * the audio contents are a complete sound sample that contains the contents/body of the 1595 * notification. This may be used in substitute of a Text-to-Speech reading of the 1596 * notification. For example if the notification represents a voice message this should point 1597 * to the audio of that message. 1598 * 1599 * The data stored under this key should be a String representation of a Uri that contains the 1600 * audio contents in one of the following formats: WAV, PCM 16-bit, AMR-WB. 1601 * 1602 * This extra is unnecessary if you are using {@code MessagingStyle} since each {@code Message} 1603 * has a field for holding data URI. That field can be used for audio. 1604 * See {@code Message#setData}. 1605 * 1606 * Example usage: 1607 * <pre> 1608 * {@code 1609 * Notification.Builder myBuilder = (build your Notification as normal); 1610 * myBuilder.getExtras().putString(EXTRA_AUDIO_CONTENTS_URI, myAudioUri.toString()); 1611 * } 1612 * </pre> 1613 */ 1614 public static final String EXTRA_AUDIO_CONTENTS_URI = "android.audioContents"; 1615 1616 /** @hide */ 1617 @SystemApi 1618 @RequiresPermission(android.Manifest.permission.SUBSTITUTE_NOTIFICATION_APP_NAME) 1619 public static final String EXTRA_SUBSTITUTE_APP_NAME = "android.substName"; 1620 1621 /** 1622 * This is set on the notifications shown by system_server about apps running foreground 1623 * services. It indicates that the notification should be shown 1624 * only if any of the given apps do not already have a properly tagged 1625 * {@link #FLAG_FOREGROUND_SERVICE} notification currently visible to the user. 1626 * This is a string array of all package names of the apps. 1627 * @hide 1628 */ 1629 public static final String EXTRA_FOREGROUND_APPS = "android.foregroundApps"; 1630 1631 @UnsupportedAppUsage 1632 private Icon mSmallIcon; 1633 @UnsupportedAppUsage 1634 private Icon mLargeIcon; 1635 private Icon mAppIcon; 1636 1637 /** Cache for whether the notification was posted by a headless system app. */ 1638 private Boolean mBelongsToHeadlessSystemApp = null; 1639 1640 @UnsupportedAppUsage 1641 private String mChannelId; 1642 private long mTimeout; 1643 1644 private String mShortcutId; 1645 private LocusId mLocusId; 1646 private CharSequence mSettingsText; 1647 1648 private BubbleMetadata mBubbleMetadata; 1649 1650 /** @hide */ 1651 @IntDef(prefix = { "GROUP_ALERT_" }, value = { 1652 GROUP_ALERT_ALL, GROUP_ALERT_CHILDREN, GROUP_ALERT_SUMMARY 1653 }) 1654 @Retention(RetentionPolicy.SOURCE) 1655 public @interface GroupAlertBehavior {} 1656 1657 /** 1658 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all notifications in a 1659 * group with sound or vibration ought to make sound or vibrate (respectively), so this 1660 * notification will not be muted when it is in a group. 1661 */ 1662 public static final int GROUP_ALERT_ALL = 0; 1663 1664 /** 1665 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that all children 1666 * notification in a group should be silenced (no sound or vibration) even if they are posted 1667 * to a {@link NotificationChannel} that has sound and/or vibration. Use this constant to 1668 * mute this notification if this notification is a group child. This must be applied to all 1669 * children notifications you want to mute. 1670 * 1671 * <p> For example, you might want to use this constant if you post a number of children 1672 * notifications at once (say, after a periodic sync), and only need to notify the user 1673 * audibly once. 1674 */ 1675 public static final int GROUP_ALERT_SUMMARY = 1; 1676 1677 /** 1678 * Constant for {@link Builder#setGroupAlertBehavior(int)}, meaning that the summary 1679 * notification in a group should be silenced (no sound or vibration) even if they are 1680 * posted to a {@link NotificationChannel} that has sound and/or vibration. Use this constant 1681 * to mute this notification if this notification is a group summary. 1682 * 1683 * <p>For example, you might want to use this constant if only the children notifications 1684 * in your group have content and the summary is only used to visually group notifications 1685 * rather than to alert the user that new information is available. 1686 */ 1687 public static final int GROUP_ALERT_CHILDREN = 2; 1688 1689 /** 1690 * Constant for the {@link Builder#setGroup(String) group key} that is added to notifications 1691 * that are not already grouped when {@link Builder#setSilent()} is used. 1692 * 1693 * @hide 1694 */ 1695 public static final String GROUP_KEY_SILENT = "silent"; 1696 1697 private int mGroupAlertBehavior = GROUP_ALERT_ALL; 1698 1699 /** 1700 * If this notification is being shown as a badge, always show as a number. 1701 */ 1702 public static final int BADGE_ICON_NONE = 0; 1703 1704 /** 1705 * If this notification is being shown as a badge, use the {@link #getSmallIcon()} to 1706 * represent this notification. 1707 */ 1708 public static final int BADGE_ICON_SMALL = 1; 1709 1710 /** 1711 * If this notification is being shown as a badge, use the {@link #getLargeIcon()} to 1712 * represent this notification. 1713 */ 1714 public static final int BADGE_ICON_LARGE = 2; 1715 private int mBadgeIcon = BADGE_ICON_NONE; 1716 1717 /** 1718 * Determines whether the platform can generate contextual actions for a notification. 1719 */ 1720 private boolean mAllowSystemGeneratedContextualActions = true; 1721 1722 /** 1723 * Structure to encapsulate a named action that can be shown as part of this notification. 1724 * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is 1725 * selected by the user. 1726 * <p> 1727 * Apps should use {@link Notification.Builder#addAction(int, CharSequence, PendingIntent)} 1728 * or {@link Notification.Builder#addAction(Notification.Action)} 1729 * to attach actions. 1730 * <p> 1731 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level {@link 1732 * android.os.Build.VERSION_CODES#S} or higher won't be able to start activities while 1733 * processing broadcast receivers or services in response to notification action clicks. To 1734 * launch an activity in those cases, provide a {@link PendingIntent} for the activity itself. 1735 */ 1736 public static class Action implements Parcelable { 1737 /** 1738 * {@link #extras} key: Keys to a {@link Parcelable} {@link ArrayList} of 1739 * {@link RemoteInput}s. 1740 * 1741 * This is intended for {@link RemoteInput}s that only accept data, meaning 1742 * {@link RemoteInput#getAllowFreeFormInput} is false, {@link RemoteInput#getChoices} 1743 * is null or empty, and {@link RemoteInput#getAllowedDataTypes} is non-null and not 1744 * empty. These {@link RemoteInput}s will be ignored by devices that do not 1745 * support non-text-based {@link RemoteInput}s. See {@link Builder#build}. 1746 * 1747 * You can test if a RemoteInput matches these constraints using 1748 * {@link RemoteInput#isDataOnly}. 1749 */ 1750 private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS"; 1751 1752 /** 1753 * No semantic action defined. 1754 */ 1755 public static final int SEMANTIC_ACTION_NONE = 0; 1756 1757 /** 1758 * {@code SemanticAction}: Reply to a conversation, chat, group, or wherever replies 1759 * may be appropriate. 1760 */ 1761 public static final int SEMANTIC_ACTION_REPLY = 1; 1762 1763 /** 1764 * {@code SemanticAction}: Mark content as read. 1765 */ 1766 public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; 1767 1768 /** 1769 * {@code SemanticAction}: Mark content as unread. 1770 */ 1771 public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; 1772 1773 /** 1774 * {@code SemanticAction}: Delete the content associated with the notification. This 1775 * could mean deleting an email, message, etc. 1776 */ 1777 public static final int SEMANTIC_ACTION_DELETE = 4; 1778 1779 /** 1780 * {@code SemanticAction}: Archive the content associated with the notification. This 1781 * could mean archiving an email, message, etc. 1782 */ 1783 public static final int SEMANTIC_ACTION_ARCHIVE = 5; 1784 1785 /** 1786 * {@code SemanticAction}: Mute the content associated with the notification. This could 1787 * mean silencing a conversation or currently playing media. 1788 */ 1789 public static final int SEMANTIC_ACTION_MUTE = 6; 1790 1791 /** 1792 * {@code SemanticAction}: Unmute the content associated with the notification. This could 1793 * mean un-silencing a conversation or currently playing media. 1794 */ 1795 public static final int SEMANTIC_ACTION_UNMUTE = 7; 1796 1797 /** 1798 * {@code SemanticAction}: Mark content with a thumbs up. 1799 */ 1800 public static final int SEMANTIC_ACTION_THUMBS_UP = 8; 1801 1802 /** 1803 * {@code SemanticAction}: Mark content with a thumbs down. 1804 */ 1805 public static final int SEMANTIC_ACTION_THUMBS_DOWN = 9; 1806 1807 /** 1808 * {@code SemanticAction}: Call a contact, group, etc. 1809 */ 1810 public static final int SEMANTIC_ACTION_CALL = 10; 1811 1812 /** 1813 * {@code SemanticAction}: Mark the conversation associated with the notification as a 1814 * priority. Note that this is only for use by the notification assistant services. The 1815 * type will be ignored for actions an app adds to its own notifications. 1816 * @hide 1817 */ 1818 @SystemApi 1819 public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; 1820 1821 /** 1822 * {@code SemanticAction}: Mark content as a potential phishing attempt. 1823 * Note that this is only for use by the notification assistant services. The type will 1824 * be ignored for actions an app adds to its own notifications. 1825 * @hide 1826 */ 1827 @SystemApi 1828 public static final int SEMANTIC_ACTION_CONVERSATION_IS_PHISHING = 12; 1829 1830 private final Bundle mExtras; 1831 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1832 private Icon mIcon; 1833 private final RemoteInput[] mRemoteInputs; 1834 private boolean mAllowGeneratedReplies = true; 1835 private final @SemanticAction int mSemanticAction; 1836 private final boolean mIsContextual; 1837 private boolean mAuthenticationRequired; 1838 1839 /** 1840 * Small icon representing the action. 1841 * 1842 * @deprecated Use {@link Action#getIcon()} instead. 1843 */ 1844 @Deprecated 1845 public int icon; 1846 1847 /** 1848 * Title of the action. 1849 */ 1850 public CharSequence title; 1851 1852 /** 1853 * Intent to send when the user invokes this action. May be null, in which case the action 1854 * may be rendered in a disabled presentation by the system UI. 1855 */ 1856 public PendingIntent actionIntent; 1857 Action(Parcel in)1858 private Action(Parcel in) { 1859 if (in.readInt() != 0) { 1860 mIcon = Icon.CREATOR.createFromParcel(in); 1861 if (mIcon.getType() == Icon.TYPE_RESOURCE) { 1862 icon = mIcon.getResId(); 1863 } 1864 } 1865 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1866 if (in.readInt() == 1) { 1867 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 1868 } 1869 mExtras = Bundle.setDefusable(in.readBundle(), true); 1870 mRemoteInputs = in.createTypedArray(RemoteInput.CREATOR); 1871 mAllowGeneratedReplies = in.readInt() == 1; 1872 mSemanticAction = in.readInt(); 1873 mIsContextual = in.readInt() == 1; 1874 mAuthenticationRequired = in.readInt() == 1; 1875 } 1876 1877 /** 1878 * @deprecated Use {@link android.app.Notification.Action.Builder}. 1879 */ 1880 @Deprecated Action(int icon, CharSequence title, @Nullable PendingIntent intent)1881 public Action(int icon, CharSequence title, @Nullable PendingIntent intent) { 1882 this(Icon.createWithResource("", icon), title, intent, new Bundle(), null, true, 1883 SEMANTIC_ACTION_NONE, false /* isContextual */, false /* requireAuth */); 1884 } 1885 1886 /** 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)1887 private Action(Icon icon, CharSequence title, PendingIntent intent, Bundle extras, 1888 RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 1889 @SemanticAction int semanticAction, boolean isContextual, 1890 boolean requireAuth) { 1891 this.mIcon = icon; 1892 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 1893 this.icon = icon.getResId(); 1894 } 1895 this.title = title; 1896 this.actionIntent = intent; 1897 this.mExtras = extras != null ? extras : new Bundle(); 1898 this.mRemoteInputs = remoteInputs; 1899 this.mAllowGeneratedReplies = allowGeneratedReplies; 1900 this.mSemanticAction = semanticAction; 1901 this.mIsContextual = isContextual; 1902 this.mAuthenticationRequired = requireAuth; 1903 } 1904 1905 /** 1906 * Return an icon representing the action. 1907 */ getIcon()1908 public Icon getIcon() { 1909 if (mIcon == null && icon != 0) { 1910 // you snuck an icon in here without using the builder; let's try to keep it 1911 mIcon = Icon.createWithResource("", icon); 1912 } 1913 return mIcon; 1914 } 1915 1916 /** 1917 * Get additional metadata carried around with this Action. 1918 */ getExtras()1919 public Bundle getExtras() { 1920 return mExtras; 1921 } 1922 1923 /** 1924 * Return whether the platform should automatically generate possible replies for this 1925 * {@link Action} 1926 */ getAllowGeneratedReplies()1927 public boolean getAllowGeneratedReplies() { 1928 return mAllowGeneratedReplies; 1929 } 1930 1931 /** 1932 * Get the list of inputs to be collected from the user when this action is sent. 1933 * May return null if no remote inputs were added. Only returns inputs which accept 1934 * a text input. For inputs which only accept data use {@link #getDataOnlyRemoteInputs}. 1935 */ getRemoteInputs()1936 public RemoteInput[] getRemoteInputs() { 1937 return mRemoteInputs; 1938 } 1939 1940 /** 1941 * Returns the {@code SemanticAction} associated with this {@link Action}. A 1942 * {@code SemanticAction} denotes what an {@link Action}'s {@link PendingIntent} will do 1943 * (eg. reply, mark as read, delete, etc). 1944 */ getSemanticAction()1945 public @SemanticAction int getSemanticAction() { 1946 return mSemanticAction; 1947 } 1948 1949 /** 1950 * Returns whether this is a contextual Action, i.e. whether the action is dependent on the 1951 * notification message body. An example of a contextual action could be an action opening a 1952 * map application with an address shown in the notification. 1953 */ isContextual()1954 public boolean isContextual() { 1955 return mIsContextual; 1956 } 1957 1958 /** 1959 * Get the list of inputs to be collected from the user that ONLY accept data when this 1960 * action is sent. These remote inputs are guaranteed to return true on a call to 1961 * {@link RemoteInput#isDataOnly}. 1962 * 1963 * Returns null if there are no data-only remote inputs. 1964 * 1965 * This method exists so that legacy RemoteInput collectors that pre-date the addition 1966 * of non-textual RemoteInputs do not access these remote inputs. 1967 */ getDataOnlyRemoteInputs()1968 public RemoteInput[] getDataOnlyRemoteInputs() { 1969 return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 1970 } 1971 1972 /** 1973 * Returns whether the OS should only send this action's {@link PendingIntent} on an 1974 * unlocked device. 1975 * 1976 * If the device is locked when the action is invoked, the OS should show the keyguard and 1977 * require successful authentication before invoking the intent. 1978 */ isAuthenticationRequired()1979 public boolean isAuthenticationRequired() { 1980 return mAuthenticationRequired; 1981 } 1982 1983 /** 1984 * Builder class for {@link Action} objects. 1985 */ 1986 public static final class Builder { 1987 @Nullable private final Icon mIcon; 1988 @Nullable private final CharSequence mTitle; 1989 @Nullable private final PendingIntent mIntent; 1990 private boolean mAllowGeneratedReplies = true; 1991 @NonNull private final Bundle mExtras; 1992 @Nullable private ArrayList<RemoteInput> mRemoteInputs; 1993 private @SemanticAction int mSemanticAction; 1994 private boolean mIsContextual; 1995 private boolean mAuthenticationRequired; 1996 1997 /** 1998 * Construct a new builder for {@link Action} object. 1999 * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, 2000 * action button icons will not be displayed on action buttons, but are still required 2001 * and are available to 2002 * {@link android.service.notification.NotificationListenerService notification listeners}, 2003 * which may display them in other contexts, for example on a wearable device. 2004 * @param icon icon to show for this action 2005 * @param title the title of the action 2006 * @param intent the {@link PendingIntent} to fire when users trigger this action. May 2007 * be null, in which case the action may be rendered in a disabled presentation by the 2008 * system UI. 2009 */ 2010 @Deprecated Builder(int icon, CharSequence title, @Nullable PendingIntent intent)2011 public Builder(int icon, CharSequence title, @Nullable PendingIntent intent) { 2012 this(Icon.createWithResource("", icon), title, intent); 2013 } 2014 2015 /** 2016 * Construct a new builder for {@link Action} object. 2017 * 2018 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 2019 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 2020 * while processing broadcast receivers or services in response to notification action 2021 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the 2022 * activity itself. 2023 * 2024 * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or 2025 * both are displayed or required, depends on where and how the action is used, and the 2026 * {@link Style} applied to the Notification. 2027 * 2028 * <p>As of Android {@link android.os.Build.VERSION_CODES#N}, action button icons 2029 * will not be displayed on action buttons, but are still required and are available 2030 * to {@link android.service.notification.NotificationListenerService notification 2031 * listeners}, which may display them in other contexts, for example on a wearable 2032 * device. 2033 * 2034 * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a 2035 * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed 2036 * with an altered in luminance to ensure proper contrast within the Notification. 2037 * 2038 * @param icon icon to show for this action 2039 * @param title the title of the action 2040 * @param intent the {@link PendingIntent} to fire when users trigger this action. May 2041 * be null, in which case the action may be rendered in a disabled presentation by the 2042 * system UI. 2043 */ Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent)2044 public Builder(Icon icon, CharSequence title, @Nullable PendingIntent intent) { 2045 this(icon, title, intent, new Bundle(), null, true, SEMANTIC_ACTION_NONE, false); 2046 } 2047 2048 /** 2049 * Construct a new builder for {@link Action} object using the fields from an 2050 * {@link Action}. 2051 * @param action the action to read fields from. 2052 */ Builder(Action action)2053 public Builder(Action action) { 2054 this(action.getIcon(), action.title, action.actionIntent, 2055 new Bundle(action.mExtras), action.getRemoteInputs(), 2056 action.getAllowGeneratedReplies(), action.getSemanticAction(), 2057 action.isAuthenticationRequired()); 2058 } 2059 Builder(@ullable Icon icon, @Nullable CharSequence title, @Nullable PendingIntent intent, @NonNull Bundle extras, @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, @SemanticAction int semanticAction, boolean authRequired)2060 private Builder(@Nullable Icon icon, @Nullable CharSequence title, 2061 @Nullable PendingIntent intent, @NonNull Bundle extras, 2062 @Nullable RemoteInput[] remoteInputs, boolean allowGeneratedReplies, 2063 @SemanticAction int semanticAction, boolean authRequired) { 2064 mIcon = icon; 2065 mTitle = title; 2066 mIntent = intent; 2067 mExtras = extras; 2068 if (remoteInputs != null) { 2069 mRemoteInputs = new ArrayList<>(remoteInputs.length); 2070 Collections.addAll(mRemoteInputs, remoteInputs); 2071 } 2072 mAllowGeneratedReplies = allowGeneratedReplies; 2073 mSemanticAction = semanticAction; 2074 mAuthenticationRequired = authRequired; 2075 } 2076 2077 /** 2078 * Merge additional metadata into this builder. 2079 * 2080 * <p>Values within the Bundle will replace existing extras values in this Builder. 2081 * 2082 * @see Notification.Action#extras 2083 */ 2084 @NonNull addExtras(Bundle extras)2085 public Builder addExtras(Bundle extras) { 2086 if (extras != null) { 2087 mExtras.putAll(extras); 2088 } 2089 return this; 2090 } 2091 2092 /** 2093 * Get the metadata Bundle used by this Builder. 2094 * 2095 * <p>The returned Bundle is shared with this Builder. 2096 */ 2097 @NonNull getExtras()2098 public Bundle getExtras() { 2099 return mExtras; 2100 } 2101 2102 /** 2103 * Add an input to be collected from the user when this action is sent. 2104 * Response values can be retrieved from the fired intent by using the 2105 * {@link RemoteInput#getResultsFromIntent} function. 2106 * @param remoteInput a {@link RemoteInput} to add to the action 2107 * @return this object for method chaining 2108 */ 2109 @NonNull addRemoteInput(RemoteInput remoteInput)2110 public Builder addRemoteInput(RemoteInput remoteInput) { 2111 if (mRemoteInputs == null) { 2112 mRemoteInputs = new ArrayList<RemoteInput>(); 2113 } 2114 mRemoteInputs.add(remoteInput); 2115 return this; 2116 } 2117 2118 /** 2119 * Set whether the platform should automatically generate possible replies to add to 2120 * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a 2121 * {@link RemoteInput}, this has no effect. 2122 * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false} 2123 * otherwise 2124 * @return this object for method chaining 2125 * The default value is {@code true} 2126 */ 2127 @NonNull setAllowGeneratedReplies(boolean allowGeneratedReplies)2128 public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) { 2129 mAllowGeneratedReplies = allowGeneratedReplies; 2130 return this; 2131 } 2132 2133 /** 2134 * Sets the {@code SemanticAction} for this {@link Action}. A 2135 * {@code SemanticAction} denotes what an {@link Action}'s 2136 * {@link PendingIntent} will do (eg. reply, mark as read, delete, etc). 2137 * @param semanticAction a SemanticAction defined within {@link Action} with 2138 * {@code SEMANTIC_ACTION_} prefixes 2139 * @return this object for method chaining 2140 */ 2141 @NonNull setSemanticAction(@emanticAction int semanticAction)2142 public Builder setSemanticAction(@SemanticAction int semanticAction) { 2143 mSemanticAction = semanticAction; 2144 return this; 2145 } 2146 2147 /** 2148 * Sets whether this {@link Action} is a contextual action, i.e. whether the action is 2149 * dependent on the notification message body. An example of a contextual action could 2150 * be an action opening a map application with an address shown in the notification. 2151 */ 2152 @NonNull setContextual(boolean isContextual)2153 public Builder setContextual(boolean isContextual) { 2154 mIsContextual = isContextual; 2155 return this; 2156 } 2157 2158 /** 2159 * Apply an extender to this action builder. Extenders may be used to add 2160 * metadata or change options on this builder. 2161 */ 2162 @NonNull extend(Extender extender)2163 public Builder extend(Extender extender) { 2164 extender.extend(this); 2165 return this; 2166 } 2167 2168 /** 2169 * Sets whether the OS should only send this action's {@link PendingIntent} on an 2170 * unlocked device. 2171 * 2172 * If this is true and the device is locked when the action is invoked, the OS will 2173 * show the keyguard and require successful authentication before invoking the intent. 2174 * If this is false and the device is locked, the OS will decide whether authentication 2175 * should be required. 2176 */ 2177 @NonNull setAuthenticationRequired(boolean authenticationRequired)2178 public Builder setAuthenticationRequired(boolean authenticationRequired) { 2179 mAuthenticationRequired = authenticationRequired; 2180 return this; 2181 } 2182 2183 /** 2184 * Throws an NPE if we are building a contextual action missing one of the fields 2185 * necessary to display the action. 2186 */ checkContextualActionNullFields()2187 private void checkContextualActionNullFields() { 2188 if (!mIsContextual) return; 2189 2190 if (mIcon == null) { 2191 throw new NullPointerException("Contextual Actions must contain a valid icon"); 2192 } 2193 2194 if (mIntent == null) { 2195 throw new NullPointerException( 2196 "Contextual Actions must contain a valid PendingIntent"); 2197 } 2198 } 2199 2200 /** 2201 * Combine all of the options that have been set and return a new {@link Action} 2202 * object. 2203 * @return the built action 2204 */ 2205 @NonNull build()2206 public Action build() { 2207 checkContextualActionNullFields(); 2208 2209 ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>(); 2210 RemoteInput[] previousDataInputs = getParcelableArrayFromBundle( 2211 mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class); 2212 if (previousDataInputs != null) { 2213 for (RemoteInput input : previousDataInputs) { 2214 dataOnlyInputs.add(input); 2215 } 2216 } 2217 List<RemoteInput> textInputs = new ArrayList<>(); 2218 if (mRemoteInputs != null) { 2219 for (RemoteInput input : mRemoteInputs) { 2220 if (input.isDataOnly()) { 2221 dataOnlyInputs.add(input); 2222 } else { 2223 textInputs.add(input); 2224 } 2225 } 2226 } 2227 if (!dataOnlyInputs.isEmpty()) { 2228 RemoteInput[] dataInputsArr = 2229 dataOnlyInputs.toArray(new RemoteInput[dataOnlyInputs.size()]); 2230 mExtras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS, dataInputsArr); 2231 } 2232 RemoteInput[] textInputsArr = textInputs.isEmpty() 2233 ? null : textInputs.toArray(new RemoteInput[textInputs.size()]); 2234 return new Action(mIcon, mTitle, mIntent, mExtras, textInputsArr, 2235 mAllowGeneratedReplies, mSemanticAction, mIsContextual, 2236 mAuthenticationRequired); 2237 } 2238 } 2239 visitUris(@onNull Consumer<Uri> visitor)2240 private void visitUris(@NonNull Consumer<Uri> visitor) { 2241 visitIconUri(visitor, getIcon()); 2242 } 2243 2244 @Override clone()2245 public Action clone() { 2246 return new Action( 2247 getIcon(), 2248 title, 2249 actionIntent, // safe to alias 2250 mExtras == null ? new Bundle() : new Bundle(mExtras), 2251 getRemoteInputs(), 2252 getAllowGeneratedReplies(), 2253 getSemanticAction(), 2254 isContextual(), 2255 isAuthenticationRequired()); 2256 } 2257 2258 @Override describeContents()2259 public int describeContents() { 2260 return 0; 2261 } 2262 2263 @Override writeToParcel(Parcel out, int flags)2264 public void writeToParcel(Parcel out, int flags) { 2265 final Icon ic = getIcon(); 2266 if (ic != null) { 2267 out.writeInt(1); 2268 ic.writeToParcel(out, 0); 2269 } else { 2270 out.writeInt(0); 2271 } 2272 TextUtils.writeToParcel(title, out, flags); 2273 if (actionIntent != null) { 2274 out.writeInt(1); 2275 actionIntent.writeToParcel(out, flags); 2276 } else { 2277 out.writeInt(0); 2278 } 2279 out.writeBundle(mExtras); 2280 out.writeTypedArray(mRemoteInputs, flags); 2281 out.writeInt(mAllowGeneratedReplies ? 1 : 0); 2282 out.writeInt(mSemanticAction); 2283 out.writeInt(mIsContextual ? 1 : 0); 2284 out.writeInt(mAuthenticationRequired ? 1 : 0); 2285 } 2286 2287 public static final @android.annotation.NonNull Parcelable.Creator<Action> CREATOR = 2288 new Parcelable.Creator<Action>() { 2289 public Action createFromParcel(Parcel in) { 2290 return new Action(in); 2291 } 2292 public Action[] newArray(int size) { 2293 return new Action[size]; 2294 } 2295 }; 2296 2297 /** 2298 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 2299 * metadata or change options on an action builder. 2300 */ 2301 public interface Extender { 2302 /** 2303 * Apply this extender to a notification action builder. 2304 * @param builder the builder to be modified. 2305 * @return the build object for chaining. 2306 */ extend(Builder builder)2307 public Builder extend(Builder builder); 2308 } 2309 2310 /** 2311 * Wearable extender for notification actions. To add extensions to an action, 2312 * create a new {@link android.app.Notification.Action.WearableExtender} object using 2313 * the {@code WearableExtender()} constructor and apply it to a 2314 * {@link android.app.Notification.Action.Builder} using 2315 * {@link android.app.Notification.Action.Builder#extend}. 2316 * 2317 * <pre class="prettyprint"> 2318 * Notification.Action action = new Notification.Action.Builder( 2319 * R.drawable.archive_all, "Archive all", actionIntent) 2320 * .extend(new Notification.Action.WearableExtender() 2321 * .setAvailableOffline(false)) 2322 * .build();</pre> 2323 */ 2324 public static final class WearableExtender implements Extender { 2325 /** Notification action extra which contains wearable extensions */ 2326 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 2327 2328 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 2329 private static final String KEY_FLAGS = "flags"; 2330 private static final String KEY_IN_PROGRESS_LABEL = "inProgressLabel"; 2331 private static final String KEY_CONFIRM_LABEL = "confirmLabel"; 2332 private static final String KEY_CANCEL_LABEL = "cancelLabel"; 2333 2334 // Flags bitwise-ored to mFlags 2335 private static final int FLAG_AVAILABLE_OFFLINE = 0x1; 2336 private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1; 2337 private static final int FLAG_HINT_DISPLAY_INLINE = 1 << 2; 2338 2339 // Default value for flags integer 2340 private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE; 2341 2342 private int mFlags = DEFAULT_FLAGS; 2343 2344 private CharSequence mInProgressLabel; 2345 private CharSequence mConfirmLabel; 2346 private CharSequence mCancelLabel; 2347 2348 /** 2349 * Create a {@link android.app.Notification.Action.WearableExtender} with default 2350 * options. 2351 */ WearableExtender()2352 public WearableExtender() { 2353 } 2354 2355 /** 2356 * Create a {@link android.app.Notification.Action.WearableExtender} by reading 2357 * wearable options present in an existing notification action. 2358 * @param action the notification action to inspect. 2359 */ WearableExtender(Action action)2360 public WearableExtender(Action action) { 2361 Bundle wearableBundle = action.getExtras().getBundle(EXTRA_WEARABLE_EXTENSIONS); 2362 if (wearableBundle != null) { 2363 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 2364 mInProgressLabel = wearableBundle.getCharSequence(KEY_IN_PROGRESS_LABEL); 2365 mConfirmLabel = wearableBundle.getCharSequence(KEY_CONFIRM_LABEL); 2366 mCancelLabel = wearableBundle.getCharSequence(KEY_CANCEL_LABEL); 2367 } 2368 } 2369 2370 /** 2371 * Apply wearable extensions to a notification action that is being built. This is 2372 * typically called by the {@link android.app.Notification.Action.Builder#extend} 2373 * method of {@link android.app.Notification.Action.Builder}. 2374 */ 2375 @Override extend(Action.Builder builder)2376 public Action.Builder extend(Action.Builder builder) { 2377 Bundle wearableBundle = new Bundle(); 2378 2379 if (mFlags != DEFAULT_FLAGS) { 2380 wearableBundle.putInt(KEY_FLAGS, mFlags); 2381 } 2382 if (mInProgressLabel != null) { 2383 wearableBundle.putCharSequence(KEY_IN_PROGRESS_LABEL, mInProgressLabel); 2384 } 2385 if (mConfirmLabel != null) { 2386 wearableBundle.putCharSequence(KEY_CONFIRM_LABEL, mConfirmLabel); 2387 } 2388 if (mCancelLabel != null) { 2389 wearableBundle.putCharSequence(KEY_CANCEL_LABEL, mCancelLabel); 2390 } 2391 2392 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 2393 return builder; 2394 } 2395 2396 @Override clone()2397 public WearableExtender clone() { 2398 WearableExtender that = new WearableExtender(); 2399 that.mFlags = this.mFlags; 2400 that.mInProgressLabel = this.mInProgressLabel; 2401 that.mConfirmLabel = this.mConfirmLabel; 2402 that.mCancelLabel = this.mCancelLabel; 2403 return that; 2404 } 2405 2406 /** 2407 * Set whether this action is available when the wearable device is not connected to 2408 * a companion device. The user can still trigger this action when the wearable device is 2409 * offline, but a visual hint will indicate that the action may not be available. 2410 * Defaults to true. 2411 */ setAvailableOffline(boolean availableOffline)2412 public WearableExtender setAvailableOffline(boolean availableOffline) { 2413 setFlag(FLAG_AVAILABLE_OFFLINE, availableOffline); 2414 return this; 2415 } 2416 2417 /** 2418 * Get whether this action is available when the wearable device is not connected to 2419 * a companion device. The user can still trigger this action when the wearable device is 2420 * offline, but a visual hint will indicate that the action may not be available. 2421 * Defaults to true. 2422 */ isAvailableOffline()2423 public boolean isAvailableOffline() { 2424 return (mFlags & FLAG_AVAILABLE_OFFLINE) != 0; 2425 } 2426 setFlag(int mask, boolean value)2427 private void setFlag(int mask, boolean value) { 2428 if (value) { 2429 mFlags |= mask; 2430 } else { 2431 mFlags &= ~mask; 2432 } 2433 } 2434 2435 /** 2436 * Set a label to display while the wearable is preparing to automatically execute the 2437 * action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2438 * 2439 * @param label the label to display while the action is being prepared to execute 2440 * @return this object for method chaining 2441 */ 2442 @Deprecated setInProgressLabel(CharSequence label)2443 public WearableExtender setInProgressLabel(CharSequence label) { 2444 mInProgressLabel = label; 2445 return this; 2446 } 2447 2448 /** 2449 * Get the label to display while the wearable is preparing to automatically execute 2450 * the action. This is usually a 'ing' verb ending in ellipsis like "Sending..." 2451 * 2452 * @return the label to display while the action is being prepared to execute 2453 */ 2454 @Deprecated getInProgressLabel()2455 public CharSequence getInProgressLabel() { 2456 return mInProgressLabel; 2457 } 2458 2459 /** 2460 * Set a label to display to confirm that the action should be executed. 2461 * This is usually an imperative verb like "Send". 2462 * 2463 * @param label the label to confirm the action should be executed 2464 * @return this object for method chaining 2465 */ 2466 @Deprecated setConfirmLabel(CharSequence label)2467 public WearableExtender setConfirmLabel(CharSequence label) { 2468 mConfirmLabel = label; 2469 return this; 2470 } 2471 2472 /** 2473 * Get the label to display to confirm that the action should be executed. 2474 * This is usually an imperative verb like "Send". 2475 * 2476 * @return the label to confirm the action should be executed 2477 */ 2478 @Deprecated getConfirmLabel()2479 public CharSequence getConfirmLabel() { 2480 return mConfirmLabel; 2481 } 2482 2483 /** 2484 * Set a label to display to cancel the action. 2485 * This is usually an imperative verb, like "Cancel". 2486 * 2487 * @param label the label to display to cancel the action 2488 * @return this object for method chaining 2489 */ 2490 @Deprecated setCancelLabel(CharSequence label)2491 public WearableExtender setCancelLabel(CharSequence label) { 2492 mCancelLabel = label; 2493 return this; 2494 } 2495 2496 /** 2497 * Get the label to display to cancel the action. 2498 * This is usually an imperative verb like "Cancel". 2499 * 2500 * @return the label to display to cancel the action 2501 */ 2502 @Deprecated getCancelLabel()2503 public CharSequence getCancelLabel() { 2504 return mCancelLabel; 2505 } 2506 2507 /** 2508 * Set a hint that this Action will launch an {@link Activity} directly, telling the 2509 * platform that it can generate the appropriate transitions. 2510 * @param hintLaunchesActivity {@code true} if the content intent will launch 2511 * an activity and transitions should be generated, false otherwise. 2512 * @return this object for method chaining 2513 */ setHintLaunchesActivity( boolean hintLaunchesActivity)2514 public WearableExtender setHintLaunchesActivity( 2515 boolean hintLaunchesActivity) { 2516 setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity); 2517 return this; 2518 } 2519 2520 /** 2521 * Get a hint that this Action will launch an {@link Activity} directly, telling the 2522 * platform that it can generate the appropriate transitions 2523 * @return {@code true} if the content intent will launch an activity and transitions 2524 * should be generated, false otherwise. The default value is {@code false} if this was 2525 * never set. 2526 */ getHintLaunchesActivity()2527 public boolean getHintLaunchesActivity() { 2528 return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0; 2529 } 2530 2531 /** 2532 * Set a hint that this Action should be displayed inline. 2533 * 2534 * @param hintDisplayInline {@code true} if action should be displayed inline, false 2535 * otherwise 2536 * @return this object for method chaining 2537 */ setHintDisplayActionInline( boolean hintDisplayInline)2538 public WearableExtender setHintDisplayActionInline( 2539 boolean hintDisplayInline) { 2540 setFlag(FLAG_HINT_DISPLAY_INLINE, hintDisplayInline); 2541 return this; 2542 } 2543 2544 /** 2545 * Get a hint that this Action should be displayed inline. 2546 * 2547 * @return {@code true} if the Action should be displayed inline, {@code false} 2548 * otherwise. The default value is {@code false} if this was never set. 2549 */ getHintDisplayActionInline()2550 public boolean getHintDisplayActionInline() { 2551 return (mFlags & FLAG_HINT_DISPLAY_INLINE) != 0; 2552 } 2553 } 2554 2555 /** 2556 * Provides meaning to an {@link Action} that hints at what the associated 2557 * {@link PendingIntent} will do. For example, an {@link Action} with a 2558 * {@link PendingIntent} that replies to a text message notification may have the 2559 * {@link #SEMANTIC_ACTION_REPLY} {@code SemanticAction} set within it. 2560 * 2561 * @hide 2562 */ 2563 @IntDef(prefix = { "SEMANTIC_ACTION_" }, value = { 2564 SEMANTIC_ACTION_NONE, 2565 SEMANTIC_ACTION_REPLY, 2566 SEMANTIC_ACTION_MARK_AS_READ, 2567 SEMANTIC_ACTION_MARK_AS_UNREAD, 2568 SEMANTIC_ACTION_DELETE, 2569 SEMANTIC_ACTION_ARCHIVE, 2570 SEMANTIC_ACTION_MUTE, 2571 SEMANTIC_ACTION_UNMUTE, 2572 SEMANTIC_ACTION_THUMBS_UP, 2573 SEMANTIC_ACTION_THUMBS_DOWN, 2574 SEMANTIC_ACTION_CALL, 2575 SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY, 2576 SEMANTIC_ACTION_CONVERSATION_IS_PHISHING 2577 }) 2578 @Retention(RetentionPolicy.SOURCE) 2579 public @interface SemanticAction {} 2580 } 2581 2582 /** 2583 * Array of all {@link Action} structures attached to this notification by 2584 * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of 2585 * {@link android.service.notification.NotificationListenerService} that provide an alternative 2586 * interface for invoking actions. 2587 */ 2588 public Action[] actions; 2589 2590 /** 2591 * Replacement version of this notification whose content will be shown 2592 * in an insecure context such as atop a secure keyguard. See {@link #visibility} 2593 * and {@link #VISIBILITY_PUBLIC}. 2594 */ 2595 public Notification publicVersion; 2596 2597 /** 2598 * Constructs a Notification object with default values. 2599 * You might want to consider using {@link Builder} instead. 2600 */ Notification()2601 public Notification() 2602 { 2603 this.when = System.currentTimeMillis(); 2604 if (Flags.sortSectionByTime()) { 2605 creationTime = when; 2606 extras.putBoolean(EXTRA_SHOW_WHEN, true); 2607 } else { 2608 this.creationTime = System.currentTimeMillis(); 2609 } 2610 this.priority = PRIORITY_DEFAULT; 2611 } 2612 2613 /** 2614 * @hide 2615 */ 2616 @UnsupportedAppUsage Notification(Context context, int icon, CharSequence tickerText, long when, CharSequence contentTitle, CharSequence contentText, Intent contentIntent)2617 public Notification(Context context, int icon, CharSequence tickerText, long when, 2618 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 2619 { 2620 if (Flags.sortSectionByTime()) { 2621 creationTime = when; 2622 extras.putBoolean(EXTRA_SHOW_WHEN, true); 2623 } 2624 new Builder(context) 2625 .setWhen(when) 2626 .setSmallIcon(icon) 2627 .setTicker(tickerText) 2628 .setContentTitle(contentTitle) 2629 .setContentText(contentText) 2630 .setContentIntent(PendingIntent.getActivity( 2631 context, 0, contentIntent, PendingIntent.FLAG_MUTABLE)) 2632 .buildInto(this); 2633 } 2634 2635 /** 2636 * Constructs a Notification object with the information needed to 2637 * have a status bar icon without the standard expanded view. 2638 * 2639 * @param icon The resource id of the icon to put in the status bar. 2640 * @param tickerText The text that flows by in the status bar when the notification first 2641 * activates. 2642 * @param when The time to show in the time field. In the System.currentTimeMillis 2643 * timebase. 2644 * 2645 * @deprecated Use {@link Builder} instead. 2646 */ 2647 @Deprecated Notification(int icon, CharSequence tickerText, long when)2648 public Notification(int icon, CharSequence tickerText, long when) 2649 { 2650 this.icon = icon; 2651 this.tickerText = tickerText; 2652 this.when = when; 2653 if (Flags.sortSectionByTime()) { 2654 creationTime = when; 2655 extras.putBoolean(EXTRA_SHOW_WHEN, true); 2656 } else { 2657 this.creationTime = System.currentTimeMillis(); 2658 } 2659 } 2660 2661 /** 2662 * Unflatten the notification from a parcel. 2663 */ 2664 @SuppressWarnings("unchecked") Notification(Parcel parcel)2665 public Notification(Parcel parcel) { 2666 // IMPORTANT: Add unmarshaling code in readFromParcel as the pending 2667 // intents in extras are always written as the last entry. 2668 readFromParcelImpl(parcel); 2669 // Must be read last! 2670 allPendingIntents = (ArraySet<PendingIntent>) parcel.readArraySet(null); 2671 } 2672 readFromParcelImpl(Parcel parcel)2673 private void readFromParcelImpl(Parcel parcel) 2674 { 2675 int version = parcel.readInt(); 2676 2677 mAllowlistToken = parcel.readStrongBinder(); 2678 if (mAllowlistToken == null) { 2679 mAllowlistToken = processAllowlistToken; 2680 } 2681 if (Flags.secureAllowlistToken()) { 2682 // Propagate this token to all pending intents that are unmarshalled from the parcel, 2683 // or keep the one we're already propagating, if that's the case. 2684 if (!parcel.hasClassCookie(PendingIntent.class)) { 2685 parcel.setClassCookie(PendingIntent.class, mAllowlistToken); 2686 } 2687 } else { 2688 // Propagate this token to all pending intents that are unmarshalled from the parcel. 2689 parcel.setClassCookie(PendingIntent.class, mAllowlistToken); 2690 } 2691 2692 when = parcel.readLong(); 2693 creationTime = parcel.readLong(); 2694 if (parcel.readInt() != 0) { 2695 mSmallIcon = Icon.CREATOR.createFromParcel(parcel); 2696 if (mSmallIcon.getType() == Icon.TYPE_RESOURCE) { 2697 icon = mSmallIcon.getResId(); 2698 } 2699 } 2700 number = parcel.readInt(); 2701 if (parcel.readInt() != 0) { 2702 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2703 } 2704 if (parcel.readInt() != 0) { 2705 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2706 } 2707 if (parcel.readInt() != 0) { 2708 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2709 } 2710 if (parcel.readInt() != 0) { 2711 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 2712 } 2713 if (parcel.readInt() != 0) { 2714 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 2715 } 2716 if (parcel.readInt() != 0) { 2717 mLargeIcon = Icon.CREATOR.createFromParcel(parcel); 2718 } 2719 defaults = parcel.readInt(); 2720 flags = parcel.readInt(); 2721 if (parcel.readInt() != 0) { 2722 sound = Uri.CREATOR.createFromParcel(parcel); 2723 } 2724 2725 audioStreamType = parcel.readInt(); 2726 if (parcel.readInt() != 0) { 2727 audioAttributes = AudioAttributes.CREATOR.createFromParcel(parcel); 2728 } 2729 vibrate = parcel.createLongArray(); 2730 ledARGB = parcel.readInt(); 2731 ledOnMS = parcel.readInt(); 2732 ledOffMS = parcel.readInt(); 2733 iconLevel = parcel.readInt(); 2734 2735 if (parcel.readInt() != 0) { 2736 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 2737 } 2738 2739 priority = parcel.readInt(); 2740 2741 category = parcel.readString8(); 2742 2743 mGroupKey = parcel.readString8(); 2744 2745 mSortKey = parcel.readString8(); 2746 2747 extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null 2748 fixDuplicateExtras(); 2749 2750 actions = parcel.createTypedArray(Action.CREATOR); // may be null 2751 2752 if (parcel.readInt() != 0) { 2753 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2754 } 2755 2756 if (parcel.readInt() != 0) { 2757 headsUpContentView = RemoteViews.CREATOR.createFromParcel(parcel); 2758 } 2759 2760 visibility = parcel.readInt(); 2761 2762 if (parcel.readInt() != 0) { 2763 publicVersion = Notification.CREATOR.createFromParcel(parcel); 2764 } 2765 2766 color = parcel.readInt(); 2767 2768 if (parcel.readInt() != 0) { 2769 mChannelId = parcel.readString8(); 2770 } 2771 mTimeout = parcel.readLong(); 2772 2773 if (parcel.readInt() != 0) { 2774 mShortcutId = parcel.readString8(); 2775 } 2776 2777 if (parcel.readInt() != 0) { 2778 mLocusId = LocusId.CREATOR.createFromParcel(parcel); 2779 } 2780 2781 mBadgeIcon = parcel.readInt(); 2782 2783 if (parcel.readInt() != 0) { 2784 mSettingsText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 2785 } 2786 2787 mGroupAlertBehavior = parcel.readInt(); 2788 if (parcel.readInt() != 0) { 2789 mBubbleMetadata = BubbleMetadata.CREATOR.createFromParcel(parcel); 2790 } 2791 2792 mAllowSystemGeneratedContextualActions = parcel.readBoolean(); 2793 2794 mFgsDeferBehavior = parcel.readInt(); 2795 } 2796 2797 @Override clone()2798 public Notification clone() { 2799 Notification that = new Notification(); 2800 cloneInto(that, true); 2801 return that; 2802 } 2803 2804 /** 2805 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 2806 * of this into that. 2807 * @hide 2808 */ cloneInto(Notification that, boolean heavy)2809 public void cloneInto(Notification that, boolean heavy) { 2810 that.mAllowlistToken = this.mAllowlistToken; 2811 that.when = this.when; 2812 that.creationTime = this.creationTime; 2813 that.mSmallIcon = this.mSmallIcon; 2814 that.number = this.number; 2815 2816 // PendingIntents are global, so there's no reason (or way) to clone them. 2817 that.contentIntent = this.contentIntent; 2818 that.deleteIntent = this.deleteIntent; 2819 that.fullScreenIntent = this.fullScreenIntent; 2820 2821 if (this.tickerText != null) { 2822 that.tickerText = this.tickerText.toString(); 2823 } 2824 if (heavy && this.tickerView != null) { 2825 that.tickerView = this.tickerView.clone(); 2826 } 2827 if (heavy && this.contentView != null) { 2828 that.contentView = this.contentView.clone(); 2829 } 2830 if (heavy && this.mLargeIcon != null) { 2831 that.mLargeIcon = this.mLargeIcon; 2832 } 2833 that.iconLevel = this.iconLevel; 2834 that.sound = this.sound; // android.net.Uri is immutable 2835 that.audioStreamType = this.audioStreamType; 2836 if (this.audioAttributes != null) { 2837 that.audioAttributes = new AudioAttributes.Builder(this.audioAttributes).build(); 2838 } 2839 2840 final long[] vibrate = this.vibrate; 2841 if (vibrate != null) { 2842 final int N = vibrate.length; 2843 final long[] vib = that.vibrate = new long[N]; 2844 System.arraycopy(vibrate, 0, vib, 0, N); 2845 } 2846 2847 that.ledARGB = this.ledARGB; 2848 that.ledOnMS = this.ledOnMS; 2849 that.ledOffMS = this.ledOffMS; 2850 that.defaults = this.defaults; 2851 2852 that.flags = this.flags; 2853 2854 that.priority = this.priority; 2855 2856 that.category = this.category; 2857 2858 that.mGroupKey = this.mGroupKey; 2859 2860 that.mSortKey = this.mSortKey; 2861 2862 if (this.extras != null) { 2863 try { 2864 that.extras = new Bundle(this.extras); 2865 // will unparcel 2866 that.extras.size(); 2867 } catch (BadParcelableException e) { 2868 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 2869 that.extras = null; 2870 } 2871 } 2872 2873 if (!ArrayUtils.isEmpty(allPendingIntents)) { 2874 that.allPendingIntents = new ArraySet<>(allPendingIntents); 2875 } 2876 2877 if (this.actions != null) { 2878 that.actions = new Action[this.actions.length]; 2879 for(int i=0; i<this.actions.length; i++) { 2880 if ( this.actions[i] != null) { 2881 that.actions[i] = this.actions[i].clone(); 2882 } 2883 } 2884 } 2885 2886 if (heavy && this.bigContentView != null) { 2887 that.bigContentView = this.bigContentView.clone(); 2888 } 2889 2890 if (heavy && this.headsUpContentView != null) { 2891 that.headsUpContentView = this.headsUpContentView.clone(); 2892 } 2893 2894 that.visibility = this.visibility; 2895 2896 if (this.publicVersion != null) { 2897 that.publicVersion = new Notification(); 2898 this.publicVersion.cloneInto(that.publicVersion, heavy); 2899 } 2900 2901 that.color = this.color; 2902 2903 that.mChannelId = this.mChannelId; 2904 that.mTimeout = this.mTimeout; 2905 that.mShortcutId = this.mShortcutId; 2906 that.mLocusId = this.mLocusId; 2907 that.mBadgeIcon = this.mBadgeIcon; 2908 that.mSettingsText = this.mSettingsText; 2909 that.mGroupAlertBehavior = this.mGroupAlertBehavior; 2910 that.mFgsDeferBehavior = this.mFgsDeferBehavior; 2911 that.mBubbleMetadata = this.mBubbleMetadata; 2912 that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; 2913 2914 if (!heavy) { 2915 that.lightenPayload(); // will clean out extras 2916 } 2917 } 2918 visitIconUri(@onNull Consumer<Uri> visitor, @Nullable Icon icon)2919 private static void visitIconUri(@NonNull Consumer<Uri> visitor, @Nullable Icon icon) { 2920 if (icon == null) return; 2921 final int iconType = icon.getType(); 2922 if (iconType == TYPE_URI || iconType == TYPE_URI_ADAPTIVE_BITMAP) { 2923 visitor.accept(icon.getUri()); 2924 } 2925 } 2926 2927 /** 2928 * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission 2929 * grants will need to be issued to ensure the recipient of this object is able to render its 2930 * contents. 2931 * See b/281044385 for more context and examples about what happens when this isn't done 2932 * correctly. 2933 * 2934 * @hide 2935 */ visitUris(@onNull Consumer<Uri> visitor)2936 public void visitUris(@NonNull Consumer<Uri> visitor) { 2937 if (publicVersion != null) { 2938 publicVersion.visitUris(visitor); 2939 } 2940 2941 visitor.accept(sound); 2942 2943 if (tickerView != null) tickerView.visitUris(visitor); 2944 if (contentView != null) contentView.visitUris(visitor); 2945 if (bigContentView != null) bigContentView.visitUris(visitor); 2946 if (headsUpContentView != null) headsUpContentView.visitUris(visitor); 2947 2948 visitIconUri(visitor, mSmallIcon); 2949 visitIconUri(visitor, mLargeIcon); 2950 2951 if (actions != null) { 2952 for (Action action : actions) { 2953 action.visitUris(visitor); 2954 } 2955 } 2956 2957 if (extras != null) { 2958 visitIconUri(visitor, extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class)); 2959 visitIconUri(visitor, extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class)); 2960 2961 // NOTE: The documentation of EXTRA_AUDIO_CONTENTS_URI explicitly says that it is a 2962 // String representation of a Uri, but the previous implementation (and unit test) of 2963 // this method has always treated it as a Uri object. Given the inconsistency, 2964 // supporting both going forward is the safest choice. 2965 Object audioContentsUri = extras.get(EXTRA_AUDIO_CONTENTS_URI); 2966 if (audioContentsUri instanceof Uri) { 2967 visitor.accept((Uri) audioContentsUri); 2968 } else if (audioContentsUri instanceof String) { 2969 visitor.accept(Uri.parse((String) audioContentsUri)); 2970 } 2971 2972 if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 2973 visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI))); 2974 } 2975 2976 ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class); 2977 if (people != null && !people.isEmpty()) { 2978 for (Person p : people) { 2979 p.visitUris(visitor); 2980 } 2981 } 2982 2983 final RemoteInputHistoryItem[] history = extras.getParcelableArray( 2984 Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 2985 RemoteInputHistoryItem.class); 2986 if (history != null) { 2987 for (int i = 0; i < history.length; i++) { 2988 RemoteInputHistoryItem item = history[i]; 2989 if (item.getUri() != null) { 2990 visitor.accept(item.getUri()); 2991 } 2992 } 2993 } 2994 2995 // Extras for MessagingStyle. We visit them even if not isStyle(MessagingStyle), since 2996 // Notification Listeners might use directly (without the isStyle check). 2997 final Person person = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); 2998 if (person != null) { 2999 person.visitUris(visitor); 3000 } 3001 3002 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, 3003 Parcelable.class); 3004 if (!ArrayUtils.isEmpty(messages)) { 3005 for (MessagingStyle.Message message : MessagingStyle.Message 3006 .getMessagesFromBundleArray(messages)) { 3007 message.visitUris(visitor); 3008 } 3009 } 3010 3011 final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, 3012 Parcelable.class); 3013 if (!ArrayUtils.isEmpty(historic)) { 3014 for (MessagingStyle.Message message : MessagingStyle.Message 3015 .getMessagesFromBundleArray(historic)) { 3016 message.visitUris(visitor); 3017 } 3018 } 3019 3020 visitIconUri(visitor, extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class)); 3021 3022 // Extras for CallStyle (same reason for visiting without checking isStyle). 3023 Person callPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); 3024 if (callPerson != null) { 3025 callPerson.visitUris(visitor); 3026 } 3027 visitIconUri(visitor, extras.getParcelable(EXTRA_VERIFICATION_ICON, Icon.class)); 3028 } 3029 3030 if (mBubbleMetadata != null) { 3031 visitIconUri(visitor, mBubbleMetadata.getIcon()); 3032 } 3033 3034 if (extras != null && extras.containsKey(WearableExtender.EXTRA_WEARABLE_EXTENSIONS)) { 3035 WearableExtender extender = new WearableExtender(this); 3036 extender.visitUris(visitor); 3037 } 3038 } 3039 3040 /** 3041 * @hide 3042 */ loadHeaderAppName(Context context)3043 public String loadHeaderAppName(Context context) { 3044 Trace.beginSection("Notification#loadHeaderAppName"); 3045 3046 try { 3047 CharSequence name = null; 3048 // Check if there is a non-empty substitute app name and return that. 3049 if (extras.containsKey(EXTRA_SUBSTITUTE_APP_NAME)) { 3050 name = extras.getString(EXTRA_SUBSTITUTE_APP_NAME); 3051 if (!TextUtils.isEmpty(name)) { 3052 return name.toString(); 3053 } 3054 } 3055 // If not, try getting the name from the app info. 3056 if (context == null) { 3057 return null; 3058 } 3059 if (TextUtils.isEmpty(name)) { 3060 ApplicationInfo info = getApplicationInfo(context); 3061 if (info != null) { 3062 final PackageManager pm = context.getPackageManager(); 3063 name = pm.getApplicationLabel(getApplicationInfo(context)); 3064 } 3065 } 3066 // If there's still nothing, ¯\_(ツ)_/¯ 3067 if (TextUtils.isEmpty(name)) { 3068 return null; 3069 } 3070 return name.toString(); 3071 } finally { 3072 Trace.endSection(); 3073 } 3074 } 3075 3076 /** 3077 * Whether this notification was posted by a headless system app. 3078 * 3079 * If we don't have enough information to figure this out, this will return false. Therefore, 3080 * false negatives are possible, but false positives should not be. 3081 * 3082 * @hide 3083 */ belongsToHeadlessSystemApp(Context context)3084 public boolean belongsToHeadlessSystemApp(Context context) { 3085 Trace.beginSection("Notification#belongsToHeadlessSystemApp"); 3086 3087 try { 3088 if (mBelongsToHeadlessSystemApp != null) { 3089 return mBelongsToHeadlessSystemApp; 3090 } 3091 3092 if (context == null) { 3093 // Without a valid context, we don't know exactly. Let's assume it doesn't belong to 3094 // a system app, but not cache the value. 3095 return false; 3096 } 3097 3098 ApplicationInfo info = getApplicationInfo(context); 3099 if (info != null) { 3100 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 3101 // It's not a system app at all. 3102 mBelongsToHeadlessSystemApp = false; 3103 } else { 3104 // If there's no launch intent, it's probably a headless app. 3105 final PackageManager pm = context.getPackageManager(); 3106 mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName) 3107 == null; 3108 } 3109 } else { 3110 // If for some reason we don't have the app info, we don't know; best assume it's 3111 // not a system app. 3112 return false; 3113 } 3114 return mBelongsToHeadlessSystemApp; 3115 } finally { 3116 Trace.endSection(); 3117 } 3118 } 3119 3120 /** 3121 * Get the resource ID of the app icon from application info. 3122 * @hide 3123 */ getHeaderAppIconRes(Context context)3124 public int getHeaderAppIconRes(Context context) { 3125 ApplicationInfo info = getApplicationInfo(context); 3126 if (info != null) { 3127 return info.icon; 3128 } 3129 return 0; 3130 } 3131 3132 /** 3133 * Load the app icon drawable from the package manager. This could result in a binder call. 3134 * @hide 3135 */ loadHeaderAppIcon(Context context)3136 public Drawable loadHeaderAppIcon(Context context) { 3137 Trace.beginSection("Notification#loadHeaderAppIcon"); 3138 3139 try { 3140 if (context == null) { 3141 Log.e(TAG, "Cannot load the app icon drawable with a null context"); 3142 return null; 3143 } 3144 final PackageManager pm = context.getPackageManager(); 3145 ApplicationInfo info = getApplicationInfo(context); 3146 if (info == null) { 3147 Log.e(TAG, "Cannot load the app icon drawable: no application info"); 3148 return null; 3149 } 3150 return pm.getApplicationIcon(info); 3151 } finally { 3152 Trace.endSection(); 3153 } 3154 } 3155 3156 /** 3157 * Fetch the application info from the notification, or the context if that isn't available. 3158 */ getApplicationInfo(Context context)3159 private ApplicationInfo getApplicationInfo(Context context) { 3160 ApplicationInfo info = null; 3161 if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { 3162 info = extras.getParcelable( 3163 EXTRA_BUILDER_APPLICATION_INFO, 3164 ApplicationInfo.class); 3165 } 3166 if (info == null) { 3167 if (context == null) { 3168 return null; 3169 } 3170 info = context.getApplicationInfo(); 3171 } 3172 return info; 3173 } 3174 3175 /** 3176 * Removes heavyweight parts of the Notification object for archival or for sending to 3177 * listeners when the full contents are not necessary. 3178 * @hide 3179 */ lightenPayload()3180 public final void lightenPayload() { 3181 tickerView = null; 3182 contentView = null; 3183 bigContentView = null; 3184 headsUpContentView = null; 3185 mLargeIcon = null; 3186 if (extras != null && !extras.isEmpty()) { 3187 final Set<String> keyset = extras.keySet(); 3188 final int N = keyset.size(); 3189 final String[] keys = keyset.toArray(new String[N]); 3190 for (int i=0; i<N; i++) { 3191 final String key = keys[i]; 3192 if (TvExtender.EXTRA_TV_EXTENDER.equals(key)) { 3193 continue; 3194 } 3195 final Object obj = extras.get(key); 3196 if (obj != null && 3197 ( obj instanceof Parcelable 3198 || obj instanceof Parcelable[] 3199 || obj instanceof SparseArray 3200 || obj instanceof ArrayList)) { 3201 extras.remove(key); 3202 } 3203 } 3204 } 3205 } 3206 3207 /** 3208 * Make sure this CharSequence is safe to put into a bundle, which basically 3209 * means it had better not be some custom Parcelable implementation. 3210 * @hide 3211 */ safeCharSequence(CharSequence cs)3212 public static CharSequence safeCharSequence(CharSequence cs) { 3213 if (cs == null) return cs; 3214 if (cs.length() > MAX_CHARSEQUENCE_LENGTH) { 3215 cs = cs.subSequence(0, MAX_CHARSEQUENCE_LENGTH); 3216 } 3217 if (cs instanceof Parcelable) { 3218 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 3219 + " instance is a custom Parcelable and not allowed in Notification"); 3220 return cs.toString(); 3221 } 3222 3223 return removeTextSizeSpans(cs); 3224 } 3225 stripStyling(@ullable CharSequence cs)3226 private static CharSequence stripStyling(@Nullable CharSequence cs) { 3227 if (cs == null) { 3228 return cs; 3229 } 3230 3231 return cs.toString(); 3232 } 3233 normalizeBigText(@ullable CharSequence charSequence)3234 private static CharSequence normalizeBigText(@Nullable CharSequence charSequence) { 3235 if (charSequence == null) { 3236 return charSequence; 3237 } 3238 3239 return NotificationBigTextNormalizer.normalizeBigText(charSequence.toString()); 3240 } 3241 removeTextSizeSpans(CharSequence charSequence)3242 private static CharSequence removeTextSizeSpans(CharSequence charSequence) { 3243 if (charSequence instanceof Spanned) { 3244 Spanned ss = (Spanned) charSequence; 3245 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 3246 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 3247 for (Object span : spans) { 3248 Object resultSpan = span; 3249 if (resultSpan instanceof CharacterStyle) { 3250 resultSpan = ((CharacterStyle) span).getUnderlying(); 3251 } 3252 if (resultSpan instanceof TextAppearanceSpan) { 3253 TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; 3254 resultSpan = new TextAppearanceSpan( 3255 originalSpan.getFamily(), 3256 originalSpan.getTextStyle(), 3257 -1, 3258 originalSpan.getTextColor(), 3259 originalSpan.getLinkTextColor()); 3260 } else if (resultSpan instanceof RelativeSizeSpan 3261 || resultSpan instanceof AbsoluteSizeSpan) { 3262 continue; 3263 } else { 3264 resultSpan = span; 3265 } 3266 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 3267 ss.getSpanFlags(span)); 3268 } 3269 return builder; 3270 } 3271 return charSequence; 3272 } 3273 describeContents()3274 public int describeContents() { 3275 return 0; 3276 } 3277 3278 /** 3279 * Flatten this notification into a parcel. 3280 */ writeToParcel(Parcel parcel, int flags)3281 public void writeToParcel(Parcel parcel, int flags) { 3282 // We need to mark all pending intents getting into the notification 3283 // system as being put there to later allow the notification ranker 3284 // to launch them and by doing so add the app to the battery saver white 3285 // list for a short period of time. The problem is that the system 3286 // cannot look into the extras as there may be parcelables there that 3287 // the platform does not know how to handle. To go around that we have 3288 // an explicit list of the pending intents in the extras bundle. 3289 PendingIntent.OnMarshaledListener addedListener = null; 3290 if (allPendingIntents == null) { 3291 addedListener = (PendingIntent intent, Parcel out, int outFlags) -> { 3292 if (parcel == out) { 3293 synchronized (this) { 3294 if (allPendingIntents == null) { 3295 allPendingIntents = new ArraySet<>(); 3296 } 3297 allPendingIntents.add(intent); 3298 } 3299 } 3300 }; 3301 PendingIntent.addOnMarshaledListener(addedListener); 3302 } 3303 try { 3304 if (Flags.secureAllowlistToken()) { 3305 boolean mustClearCookie = false; 3306 if (!parcel.hasClassCookie(Notification.class)) { 3307 // This is the "root" notification, and not an "inner" notification (including 3308 // publicVersion or anything else that might be embedded in extras). So we want 3309 // to use its token for every inner notification (might be null). 3310 parcel.setClassCookie(Notification.class, mAllowlistToken); 3311 mustClearCookie = true; 3312 } 3313 try { 3314 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 3315 // want to intercept all pending events written to the parcel. 3316 writeToParcelImpl(parcel, flags); 3317 } finally { 3318 if (mustClearCookie) { 3319 parcel.removeClassCookie(Notification.class, mAllowlistToken); 3320 } 3321 } 3322 } else { 3323 // IMPORTANT: Add marshaling code in writeToParcelImpl as we 3324 // want to intercept all pending events written to the parcel. 3325 writeToParcelImpl(parcel, flags); 3326 } 3327 3328 synchronized (this) { 3329 // Must be written last! 3330 parcel.writeArraySet(allPendingIntents); 3331 } 3332 } finally { 3333 if (addedListener != null) { 3334 PendingIntent.removeOnMarshaledListener(addedListener); 3335 } 3336 } 3337 } 3338 writeToParcelImpl(Parcel parcel, int flags)3339 private void writeToParcelImpl(Parcel parcel, int flags) { 3340 parcel.writeInt(1); 3341 3342 if (Flags.secureAllowlistToken()) { 3343 // Always use the same token as the root notification (might be null). 3344 IBinder rootNotificationToken = (IBinder) parcel.getClassCookie(Notification.class); 3345 parcel.writeStrongBinder(rootNotificationToken); 3346 } else { 3347 parcel.writeStrongBinder(mAllowlistToken); 3348 } 3349 3350 parcel.writeLong(when); 3351 parcel.writeLong(creationTime); 3352 if (mSmallIcon == null && icon != 0) { 3353 // you snuck an icon in here without using the builder; let's try to keep it 3354 mSmallIcon = Icon.createWithResource("", icon); 3355 } 3356 if (mSmallIcon != null) { 3357 parcel.writeInt(1); 3358 mSmallIcon.writeToParcel(parcel, 0); 3359 } else { 3360 parcel.writeInt(0); 3361 } 3362 parcel.writeInt(number); 3363 if (contentIntent != null) { 3364 parcel.writeInt(1); 3365 contentIntent.writeToParcel(parcel, 0); 3366 } else { 3367 parcel.writeInt(0); 3368 } 3369 if (deleteIntent != null) { 3370 parcel.writeInt(1); 3371 deleteIntent.writeToParcel(parcel, 0); 3372 } else { 3373 parcel.writeInt(0); 3374 } 3375 if (tickerText != null) { 3376 parcel.writeInt(1); 3377 TextUtils.writeToParcel(tickerText, parcel, flags); 3378 } else { 3379 parcel.writeInt(0); 3380 } 3381 if (tickerView != null) { 3382 parcel.writeInt(1); 3383 tickerView.writeToParcel(parcel, 0); 3384 } else { 3385 parcel.writeInt(0); 3386 } 3387 if (contentView != null) { 3388 parcel.writeInt(1); 3389 contentView.writeToParcel(parcel, 0); 3390 } else { 3391 parcel.writeInt(0); 3392 } 3393 if (mLargeIcon == null && largeIcon != null) { 3394 // you snuck an icon in here without using the builder; let's try to keep it 3395 mLargeIcon = Icon.createWithBitmap(largeIcon); 3396 } 3397 if (mLargeIcon != null) { 3398 parcel.writeInt(1); 3399 mLargeIcon.writeToParcel(parcel, 0); 3400 } else { 3401 parcel.writeInt(0); 3402 } 3403 3404 parcel.writeInt(defaults); 3405 parcel.writeInt(this.flags); 3406 3407 if (sound != null) { 3408 parcel.writeInt(1); 3409 sound.writeToParcel(parcel, 0); 3410 } else { 3411 parcel.writeInt(0); 3412 } 3413 parcel.writeInt(audioStreamType); 3414 3415 if (audioAttributes != null) { 3416 parcel.writeInt(1); 3417 audioAttributes.writeToParcel(parcel, 0); 3418 } else { 3419 parcel.writeInt(0); 3420 } 3421 3422 parcel.writeLongArray(vibrate); 3423 parcel.writeInt(ledARGB); 3424 parcel.writeInt(ledOnMS); 3425 parcel.writeInt(ledOffMS); 3426 parcel.writeInt(iconLevel); 3427 3428 if (fullScreenIntent != null) { 3429 parcel.writeInt(1); 3430 fullScreenIntent.writeToParcel(parcel, 0); 3431 } else { 3432 parcel.writeInt(0); 3433 } 3434 3435 parcel.writeInt(priority); 3436 3437 parcel.writeString8(category); 3438 3439 parcel.writeString8(mGroupKey); 3440 3441 parcel.writeString8(mSortKey); 3442 3443 parcel.writeBundle(extras); // null ok 3444 3445 parcel.writeTypedArray(actions, 0); // null ok 3446 3447 if (bigContentView != null) { 3448 parcel.writeInt(1); 3449 bigContentView.writeToParcel(parcel, 0); 3450 } else { 3451 parcel.writeInt(0); 3452 } 3453 3454 if (headsUpContentView != null) { 3455 parcel.writeInt(1); 3456 headsUpContentView.writeToParcel(parcel, 0); 3457 } else { 3458 parcel.writeInt(0); 3459 } 3460 3461 parcel.writeInt(visibility); 3462 3463 if (publicVersion != null) { 3464 parcel.writeInt(1); 3465 publicVersion.writeToParcel(parcel, 0); 3466 } else { 3467 parcel.writeInt(0); 3468 } 3469 3470 parcel.writeInt(color); 3471 3472 if (mChannelId != null) { 3473 parcel.writeInt(1); 3474 parcel.writeString8(mChannelId); 3475 } else { 3476 parcel.writeInt(0); 3477 } 3478 parcel.writeLong(mTimeout); 3479 3480 if (mShortcutId != null) { 3481 parcel.writeInt(1); 3482 parcel.writeString8(mShortcutId); 3483 } else { 3484 parcel.writeInt(0); 3485 } 3486 3487 if (mLocusId != null) { 3488 parcel.writeInt(1); 3489 mLocusId.writeToParcel(parcel, 0); 3490 } else { 3491 parcel.writeInt(0); 3492 } 3493 3494 parcel.writeInt(mBadgeIcon); 3495 3496 if (mSettingsText != null) { 3497 parcel.writeInt(1); 3498 TextUtils.writeToParcel(mSettingsText, parcel, flags); 3499 } else { 3500 parcel.writeInt(0); 3501 } 3502 3503 parcel.writeInt(mGroupAlertBehavior); 3504 3505 if (mBubbleMetadata != null) { 3506 parcel.writeInt(1); 3507 mBubbleMetadata.writeToParcel(parcel, 0); 3508 } else { 3509 parcel.writeInt(0); 3510 } 3511 3512 parcel.writeBoolean(mAllowSystemGeneratedContextualActions); 3513 3514 parcel.writeInt(mFgsDeferBehavior); 3515 3516 // mUsesStandardHeader is not written because it should be recomputed in listeners 3517 } 3518 3519 /** 3520 * Parcelable.Creator that instantiates Notification objects 3521 */ 3522 public static final @android.annotation.NonNull Parcelable.Creator<Notification> CREATOR 3523 = new Parcelable.Creator<Notification>() 3524 { 3525 public Notification createFromParcel(Parcel parcel) 3526 { 3527 return new Notification(parcel); 3528 } 3529 3530 public Notification[] newArray(int size) 3531 { 3532 return new Notification[size]; 3533 } 3534 }; 3535 3536 /** 3537 * @hide 3538 */ areActionsVisiblyDifferent(Notification first, Notification second)3539 public static boolean areActionsVisiblyDifferent(Notification first, Notification second) { 3540 Notification.Action[] firstAs = first.actions; 3541 Notification.Action[] secondAs = second.actions; 3542 if (firstAs == null && secondAs != null || firstAs != null && secondAs == null) { 3543 return true; 3544 } 3545 if (firstAs != null && secondAs != null) { 3546 if (firstAs.length != secondAs.length) { 3547 return true; 3548 } 3549 for (int i = 0; i < firstAs.length; i++) { 3550 if (!Objects.equals(String.valueOf(firstAs[i].title), 3551 String.valueOf(secondAs[i].title))) { 3552 return true; 3553 } 3554 RemoteInput[] firstRs = firstAs[i].getRemoteInputs(); 3555 RemoteInput[] secondRs = secondAs[i].getRemoteInputs(); 3556 if (firstRs == null) { 3557 firstRs = new RemoteInput[0]; 3558 } 3559 if (secondRs == null) { 3560 secondRs = new RemoteInput[0]; 3561 } 3562 if (firstRs.length != secondRs.length) { 3563 return true; 3564 } 3565 for (int j = 0; j < firstRs.length; j++) { 3566 if (!Objects.equals(String.valueOf(firstRs[j].getLabel()), 3567 String.valueOf(secondRs[j].getLabel()))) { 3568 return true; 3569 } 3570 } 3571 } 3572 } 3573 return false; 3574 } 3575 3576 /** 3577 * @hide 3578 */ areIconsDifferent(Notification first, Notification second)3579 public static boolean areIconsDifferent(Notification first, Notification second) { 3580 return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon()) 3581 || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon()); 3582 } 3583 3584 /** 3585 * Note that we aren't actually comparing the contents of the bitmaps here; this is only a 3586 * cursory inspection. We will not return false negatives, but false positives are likely. 3587 */ areIconsMaybeDifferent(Icon a, Icon b)3588 private static boolean areIconsMaybeDifferent(Icon a, Icon b) { 3589 if (a == b) { 3590 return false; 3591 } 3592 if (a == null || b == null) { 3593 return true; 3594 } 3595 if (a.sameAs(b)) { 3596 return false; 3597 } 3598 final int aType = a.getType(); 3599 if (aType != b.getType()) { 3600 return true; 3601 } 3602 if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) { 3603 final Bitmap aBitmap = a.getBitmap(); 3604 final Bitmap bBitmap = b.getBitmap(); 3605 return aBitmap.getWidth() != bBitmap.getWidth() 3606 || aBitmap.getHeight() != bBitmap.getHeight() 3607 || aBitmap.getConfig() != bBitmap.getConfig() 3608 || aBitmap.getGenerationId() != bBitmap.getGenerationId(); 3609 } 3610 return true; 3611 } 3612 3613 /** 3614 * @hide 3615 */ areStyledNotificationsVisiblyDifferent(Builder first, Builder second)3616 public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { 3617 if (first.getStyle() == null) { 3618 return second.getStyle() != null; 3619 } 3620 if (second.getStyle() == null) { 3621 return true; 3622 } 3623 return first.getStyle().areNotificationsVisiblyDifferent(second.getStyle()); 3624 } 3625 3626 /** 3627 * @hide 3628 */ areRemoteViewsChanged(Builder first, Builder second)3629 public static boolean areRemoteViewsChanged(Builder first, Builder second) { 3630 if (!Objects.equals(first.usesStandardHeader(), second.usesStandardHeader())) { 3631 return true; 3632 } 3633 3634 if (areRemoteViewsChanged(first.mN.contentView, second.mN.contentView)) { 3635 return true; 3636 } 3637 if (areRemoteViewsChanged(first.mN.bigContentView, second.mN.bigContentView)) { 3638 return true; 3639 } 3640 if (areRemoteViewsChanged(first.mN.headsUpContentView, second.mN.headsUpContentView)) { 3641 return true; 3642 } 3643 3644 return false; 3645 } 3646 areRemoteViewsChanged(RemoteViews first, RemoteViews second)3647 private static boolean areRemoteViewsChanged(RemoteViews first, RemoteViews second) { 3648 if (first == null && second == null) { 3649 return false; 3650 } 3651 if (first == null && second != null || first != null && second == null) { 3652 return true; 3653 } 3654 3655 if (!Objects.equals(first.getLayoutId(), second.getLayoutId())) { 3656 return true; 3657 } 3658 3659 if (!Objects.equals(first.getSequenceNumber(), second.getSequenceNumber())) { 3660 return true; 3661 } 3662 3663 return false; 3664 } 3665 3666 /** 3667 * Parcelling creates multiple copies of objects in {@code extras}. Fix them. 3668 * <p> 3669 * For backwards compatibility {@code extras} holds some references to "real" member data such 3670 * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly 3671 * fine as long as the object stays in one process. 3672 * <p> 3673 * However, once the notification goes into a parcel each reference gets marshalled separately, 3674 * wasting memory. Especially with large images on Auto and TV, this is worth fixing. 3675 */ fixDuplicateExtras()3676 private void fixDuplicateExtras() { 3677 if (extras != null) { 3678 fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); 3679 } 3680 } 3681 3682 /** 3683 * If we find an extra that's exactly the same as one of the "real" fields but refers to a 3684 * separate object, replace it with the field's version to avoid holding duplicate copies. 3685 */ fixDuplicateExtra(@ullable Parcelable original, @NonNull String extraName)3686 private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { 3687 if (original != null && extras.getParcelable(extraName, Parcelable.class) != null) { 3688 extras.putParcelable(extraName, original); 3689 } 3690 } 3691 3692 /** 3693 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 3694 * layout. 3695 * 3696 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 3697 * in the view.</p> 3698 * @param context The context for your application / activity. 3699 * @param contentTitle The title that goes in the expanded entry. 3700 * @param contentText The text that goes in the expanded entry. 3701 * @param contentIntent The intent to launch when the user clicks the expanded notification. 3702 * If this is an activity, it must include the 3703 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 3704 * that you take care of task management as described in the 3705 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 3706 * Stack</a> document. 3707 * 3708 * @deprecated Use {@link Builder} instead. 3709 * @removed 3710 */ 3711 @Deprecated setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent)3712 public void setLatestEventInfo(Context context, 3713 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 3714 if (context.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1){ 3715 Log.e(TAG, "setLatestEventInfo() is deprecated and you should feel deprecated.", 3716 new Throwable()); 3717 } 3718 3719 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 3720 extras.putBoolean(EXTRA_SHOW_WHEN, true); 3721 } 3722 3723 // ensure that any information already set directly is preserved 3724 final Notification.Builder builder = new Notification.Builder(context, this); 3725 3726 // now apply the latestEventInfo fields 3727 if (contentTitle != null) { 3728 builder.setContentTitle(contentTitle); 3729 } 3730 if (contentText != null) { 3731 builder.setContentText(contentText); 3732 } 3733 builder.setContentIntent(contentIntent); 3734 3735 builder.build(); // callers expect this notification to be ready to use 3736 } 3737 3738 /** 3739 * Sets the token used for background operations for the pending intents associated with this 3740 * notification. 3741 * 3742 * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally 3743 * populated by unparceling (and also used there). Any other usage is suspect. 3744 * 3745 * @hide 3746 */ overrideAllowlistToken(IBinder token)3747 public void overrideAllowlistToken(IBinder token) { 3748 mAllowlistToken = token; 3749 if (publicVersion != null) { 3750 publicVersion.overrideAllowlistToken(token); 3751 } 3752 } 3753 3754 /** @hide */ getAllowlistToken()3755 public IBinder getAllowlistToken() { 3756 return mAllowlistToken; 3757 } 3758 3759 /** 3760 * @hide 3761 */ addFieldsFromContext(Context context, Notification notification)3762 public static void addFieldsFromContext(Context context, Notification notification) { 3763 addFieldsFromContext(context.getApplicationInfo(), notification); 3764 } 3765 3766 /** 3767 * @hide 3768 */ addFieldsFromContext(ApplicationInfo ai, Notification notification)3769 public static void addFieldsFromContext(ApplicationInfo ai, Notification notification) { 3770 notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai); 3771 } 3772 3773 /** 3774 * @hide 3775 */ dumpDebug(ProtoOutputStream proto, long fieldId)3776 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 3777 long token = proto.start(fieldId); 3778 proto.write(NotificationProto.CHANNEL_ID, getChannelId()); 3779 proto.write(NotificationProto.HAS_TICKER_TEXT, this.tickerText != null); 3780 proto.write(NotificationProto.FLAGS, this.flags); 3781 proto.write(NotificationProto.COLOR, this.color); 3782 proto.write(NotificationProto.CATEGORY, this.category); 3783 proto.write(NotificationProto.GROUP_KEY, this.mGroupKey); 3784 proto.write(NotificationProto.SORT_KEY, this.mSortKey); 3785 if (this.actions != null) { 3786 proto.write(NotificationProto.ACTION_LENGTH, this.actions.length); 3787 } 3788 if (this.visibility >= VISIBILITY_SECRET && this.visibility <= VISIBILITY_PUBLIC) { 3789 proto.write(NotificationProto.VISIBILITY, this.visibility); 3790 } 3791 if (publicVersion != null) { 3792 publicVersion.dumpDebug(proto, NotificationProto.PUBLIC_VERSION); 3793 } 3794 proto.end(token); 3795 } 3796 3797 @Override toString()3798 public String toString() { 3799 StringBuilder sb = new StringBuilder(); 3800 sb.append("Notification(channel="); 3801 sb.append(getChannelId()); 3802 sb.append(" shortcut="); 3803 sb.append(getShortcutId()); 3804 sb.append(" contentView="); 3805 if (contentView != null) { 3806 sb.append(contentView.getPackage()); 3807 sb.append("/0x"); 3808 sb.append(Integer.toHexString(contentView.getLayoutId())); 3809 } else { 3810 sb.append("null"); 3811 } 3812 sb.append(" vibrate="); 3813 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 3814 sb.append("default"); 3815 } else if (this.vibrate != null) { 3816 int N = this.vibrate.length-1; 3817 sb.append("["); 3818 for (int i=0; i<N; i++) { 3819 sb.append(this.vibrate[i]); 3820 sb.append(','); 3821 } 3822 if (N != -1) { 3823 sb.append(this.vibrate[N]); 3824 } 3825 sb.append("]"); 3826 } else { 3827 sb.append("null"); 3828 } 3829 sb.append(" sound="); 3830 if ((this.defaults & DEFAULT_SOUND) != 0) { 3831 sb.append("default"); 3832 } else if (this.sound != null) { 3833 sb.append(this.sound.toString()); 3834 } else { 3835 sb.append("null"); 3836 } 3837 if (this.tickerText != null) { 3838 sb.append(" tick"); 3839 } 3840 sb.append(" defaults="); 3841 sb.append(defaultsToString(this.defaults)); 3842 sb.append(" flags="); 3843 sb.append(flagsToString(this.flags)); 3844 sb.append(String.format(" color=0x%08x", this.color)); 3845 if (this.category != null) { 3846 sb.append(" category="); 3847 sb.append(this.category); 3848 } 3849 if (this.mGroupKey != null) { 3850 sb.append(" groupKey="); 3851 sb.append(this.mGroupKey); 3852 } 3853 if (this.mSortKey != null) { 3854 sb.append(" sortKey="); 3855 sb.append(this.mSortKey); 3856 } 3857 if (actions != null) { 3858 sb.append(" actions="); 3859 sb.append(actions.length); 3860 } 3861 sb.append(" vis="); 3862 sb.append(visibilityToString(this.visibility)); 3863 if (this.publicVersion != null) { 3864 sb.append(" publicVersion="); 3865 sb.append(publicVersion.toString()); 3866 } 3867 if (this.mLocusId != null) { 3868 sb.append(" locusId="); 3869 sb.append(this.mLocusId); // LocusId.toString() is PII safe. 3870 } 3871 sb.append(")"); 3872 return sb.toString(); 3873 } 3874 3875 /** 3876 * {@hide} 3877 */ visibilityToString(int vis)3878 public static String visibilityToString(int vis) { 3879 switch (vis) { 3880 case VISIBILITY_PRIVATE: 3881 return "PRIVATE"; 3882 case VISIBILITY_PUBLIC: 3883 return "PUBLIC"; 3884 case VISIBILITY_SECRET: 3885 return "SECRET"; 3886 default: 3887 return "UNKNOWN(" + String.valueOf(vis) + ")"; 3888 } 3889 } 3890 3891 /** 3892 * {@hide} 3893 */ priorityToString(@riority int pri)3894 public static String priorityToString(@Priority int pri) { 3895 switch (pri) { 3896 case PRIORITY_MIN: 3897 return "MIN"; 3898 case PRIORITY_LOW: 3899 return "LOW"; 3900 case PRIORITY_DEFAULT: 3901 return "DEFAULT"; 3902 case PRIORITY_HIGH: 3903 return "HIGH"; 3904 case PRIORITY_MAX: 3905 return "MAX"; 3906 default: 3907 return "UNKNOWN(" + String.valueOf(pri) + ")"; 3908 } 3909 } 3910 3911 /** 3912 * {@hide} 3913 */ flagsToString(@otificationFlags int flags)3914 public static String flagsToString(@NotificationFlags int flags) { 3915 final List<String> flagStrings = new ArrayList<String>(); 3916 if ((flags & FLAG_SHOW_LIGHTS) != 0) { 3917 flagStrings.add("SHOW_LIGHTS"); 3918 flags &= ~FLAG_SHOW_LIGHTS; 3919 } 3920 if ((flags & FLAG_ONGOING_EVENT) != 0) { 3921 flagStrings.add("ONGOING_EVENT"); 3922 flags &= ~FLAG_ONGOING_EVENT; 3923 } 3924 if ((flags & FLAG_INSISTENT) != 0) { 3925 flagStrings.add("INSISTENT"); 3926 flags &= ~FLAG_INSISTENT; 3927 } 3928 if ((flags & FLAG_ONLY_ALERT_ONCE) != 0) { 3929 flagStrings.add("ONLY_ALERT_ONCE"); 3930 flags &= ~FLAG_ONLY_ALERT_ONCE; 3931 } 3932 if ((flags & FLAG_AUTO_CANCEL) != 0) { 3933 flagStrings.add("AUTO_CANCEL"); 3934 flags &= ~FLAG_AUTO_CANCEL; 3935 } 3936 if ((flags & FLAG_NO_CLEAR) != 0) { 3937 flagStrings.add("NO_CLEAR"); 3938 flags &= ~FLAG_NO_CLEAR; 3939 } 3940 if ((flags & FLAG_FOREGROUND_SERVICE) != 0) { 3941 flagStrings.add("FOREGROUND_SERVICE"); 3942 flags &= ~FLAG_FOREGROUND_SERVICE; 3943 } 3944 if ((flags & FLAG_HIGH_PRIORITY) != 0) { 3945 flagStrings.add("HIGH_PRIORITY"); 3946 flags &= ~FLAG_HIGH_PRIORITY; 3947 } 3948 if ((flags & FLAG_LOCAL_ONLY) != 0) { 3949 flagStrings.add("LOCAL_ONLY"); 3950 flags &= ~FLAG_LOCAL_ONLY; 3951 } 3952 if ((flags & FLAG_GROUP_SUMMARY) != 0) { 3953 flagStrings.add("GROUP_SUMMARY"); 3954 flags &= ~FLAG_GROUP_SUMMARY; 3955 } 3956 if ((flags & FLAG_AUTOGROUP_SUMMARY) != 0) { 3957 flagStrings.add("AUTOGROUP_SUMMARY"); 3958 flags &= ~FLAG_AUTOGROUP_SUMMARY; 3959 } 3960 if ((flags & FLAG_CAN_COLORIZE) != 0) { 3961 flagStrings.add("CAN_COLORIZE"); 3962 flags &= ~FLAG_CAN_COLORIZE; 3963 } 3964 if ((flags & FLAG_BUBBLE) != 0) { 3965 flagStrings.add("BUBBLE"); 3966 flags &= ~FLAG_BUBBLE; 3967 } 3968 if ((flags & FLAG_NO_DISMISS) != 0) { 3969 flagStrings.add("NO_DISMISS"); 3970 flags &= ~FLAG_NO_DISMISS; 3971 } 3972 if ((flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0) { 3973 flagStrings.add("FSI_REQUESTED_BUT_DENIED"); 3974 flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED; 3975 } 3976 if ((flags & FLAG_USER_INITIATED_JOB) != 0) { 3977 flagStrings.add("USER_INITIATED_JOB"); 3978 flags &= ~FLAG_USER_INITIATED_JOB; 3979 } 3980 if (Flags.lifetimeExtensionRefactor()) { 3981 if ((flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) != 0) { 3982 flagStrings.add("LIFETIME_EXTENDED_BY_DIRECT_REPLY"); 3983 flags &= ~FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY; 3984 } 3985 } 3986 3987 if (flagStrings.isEmpty()) { 3988 return "0"; 3989 } 3990 3991 if (flags != 0) { 3992 flagStrings.add(String.format("UNKNOWN(0x%08x)", flags)); 3993 } 3994 3995 return String.join("|", flagStrings); 3996 } 3997 3998 /** @hide */ defaultsToString(int defaults)3999 public static String defaultsToString(int defaults) { 4000 final List<String> defaultStrings = new ArrayList<String>(); 4001 if ((defaults & DEFAULT_ALL) == DEFAULT_ALL) { 4002 defaultStrings.add("ALL"); 4003 defaults &= ~DEFAULT_ALL; 4004 } 4005 if ((defaults & DEFAULT_SOUND) != 0) { 4006 defaultStrings.add("SOUND"); 4007 defaults &= ~DEFAULT_SOUND; 4008 } 4009 if ((defaults & DEFAULT_VIBRATE) != 0) { 4010 defaultStrings.add("VIBRATE"); 4011 defaults &= ~DEFAULT_VIBRATE; 4012 } 4013 if ((defaults & DEFAULT_LIGHTS) != 0) { 4014 defaultStrings.add("LIGHTS"); 4015 defaults &= ~DEFAULT_LIGHTS; 4016 } 4017 4018 if (defaultStrings.isEmpty()) { 4019 return "0"; 4020 } 4021 4022 if (defaults != 0) { 4023 defaultStrings.add(String.format("UNKNOWN(0x%08x)", defaults)); 4024 } 4025 4026 return String.join("|", defaultStrings); 4027 } 4028 4029 /** 4030 * @hide 4031 */ hasCompletedProgress()4032 public boolean hasCompletedProgress() { 4033 // not a progress notification; can't be complete 4034 if (!extras.containsKey(EXTRA_PROGRESS) 4035 || !extras.containsKey(EXTRA_PROGRESS_MAX)) { 4036 return false; 4037 } 4038 // many apps use max 0 for 'indeterminate'; not complete 4039 if (extras.getInt(EXTRA_PROGRESS_MAX) == 0) { 4040 return false; 4041 } 4042 return extras.getInt(EXTRA_PROGRESS) == extras.getInt(EXTRA_PROGRESS_MAX); 4043 } 4044 4045 /** @removed */ 4046 @Deprecated getChannel()4047 public String getChannel() { 4048 return mChannelId; 4049 } 4050 4051 /** 4052 * Returns the id of the channel this notification posts to. 4053 */ getChannelId()4054 public String getChannelId() { 4055 return mChannelId; 4056 } 4057 4058 /** @removed */ 4059 @Deprecated getTimeout()4060 public long getTimeout() { 4061 return mTimeout; 4062 } 4063 4064 /** 4065 * Returns the duration from posting after which this notification should be canceled by the 4066 * system, if it's not canceled already. 4067 */ getTimeoutAfter()4068 public long getTimeoutAfter() { 4069 return mTimeout; 4070 } 4071 4072 /** 4073 * @hide 4074 */ setTimeoutAfter(long timeout)4075 public void setTimeoutAfter(long timeout) { 4076 mTimeout = timeout; 4077 } 4078 4079 /** 4080 * Returns what icon should be shown for this notification if it is being displayed in a 4081 * Launcher that supports badging. Will be one of {@link #BADGE_ICON_NONE}, 4082 * {@link #BADGE_ICON_SMALL}, or {@link #BADGE_ICON_LARGE}. 4083 */ getBadgeIconType()4084 public int getBadgeIconType() { 4085 return mBadgeIcon; 4086 } 4087 4088 /** 4089 * Returns the {@link ShortcutInfo#getId() id} that this notification supersedes, if any. 4090 * 4091 * <p>Used by some Launchers that display notification content to hide shortcuts that duplicate 4092 * notifications. 4093 */ getShortcutId()4094 public String getShortcutId() { 4095 return mShortcutId; 4096 } 4097 4098 /** 4099 * Gets the {@link LocusId} associated with this notification. 4100 * 4101 * <p>Used by the device's intelligence services to correlate objects (such as 4102 * {@link ShortcutInfo} and {@link ContentCaptureContext}) that are correlated. 4103 */ 4104 @Nullable getLocusId()4105 public LocusId getLocusId() { 4106 return mLocusId; 4107 } 4108 4109 /** 4110 * Returns the settings text provided to {@link Builder#setSettingsText(CharSequence)}. 4111 */ getSettingsText()4112 public CharSequence getSettingsText() { 4113 return mSettingsText; 4114 } 4115 4116 /** 4117 * Returns which type of notifications in a group are responsible for audibly alerting the 4118 * user. See {@link #GROUP_ALERT_ALL}, {@link #GROUP_ALERT_CHILDREN}, 4119 * {@link #GROUP_ALERT_SUMMARY}. 4120 */ getGroupAlertBehavior()4121 public @GroupAlertBehavior int getGroupAlertBehavior() { 4122 return mGroupAlertBehavior; 4123 } 4124 4125 /** 4126 * Returns the bubble metadata that will be used to display app content in a floating window 4127 * over the existing foreground activity. 4128 */ 4129 @Nullable getBubbleMetadata()4130 public BubbleMetadata getBubbleMetadata() { 4131 return mBubbleMetadata; 4132 } 4133 4134 /** 4135 * Sets the {@link BubbleMetadata} for this notification. 4136 * @hide 4137 */ setBubbleMetadata(BubbleMetadata data)4138 public void setBubbleMetadata(BubbleMetadata data) { 4139 mBubbleMetadata = data; 4140 } 4141 4142 /** 4143 * Returns whether the platform is allowed (by the app developer) to generate contextual actions 4144 * for this notification. 4145 */ getAllowSystemGeneratedContextualActions()4146 public boolean getAllowSystemGeneratedContextualActions() { 4147 return mAllowSystemGeneratedContextualActions; 4148 } 4149 4150 /** 4151 * The small icon representing this notification in the status bar and content view. 4152 * 4153 * @return the small icon representing this notification. 4154 * 4155 * @see Builder#getSmallIcon() 4156 * @see Builder#setSmallIcon(Icon) 4157 */ getSmallIcon()4158 public Icon getSmallIcon() { 4159 return mSmallIcon; 4160 } 4161 4162 /** 4163 * Used when notifying to clean up legacy small icons. 4164 * @hide 4165 */ 4166 @UnsupportedAppUsage setSmallIcon(Icon icon)4167 public void setSmallIcon(Icon icon) { 4168 mSmallIcon = icon; 4169 } 4170 4171 /** 4172 * The colored app icon that can replace the small icon in the notification starting in V. 4173 * 4174 * Before using this value, you should first check whether it's actually being used by the 4175 * notification by calling {@link Notification#shouldUseAppIcon()}. 4176 * 4177 * @hide 4178 */ getAppIcon()4179 public Icon getAppIcon() { 4180 if (mAppIcon != null) { 4181 return mAppIcon; 4182 } 4183 // If the app icon hasn't been loaded yet, check if we can load it without a context. 4184 if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) { 4185 final ApplicationInfo info = extras.getParcelable( 4186 EXTRA_BUILDER_APPLICATION_INFO, 4187 ApplicationInfo.class); 4188 if (info != null) { 4189 int appIconRes = info.icon; 4190 if (appIconRes == 0) { 4191 Log.w(TAG, "Failed to get the app icon: no icon in application info"); 4192 return null; 4193 } 4194 mAppIcon = Icon.createWithResource(info.packageName, appIconRes); 4195 return mAppIcon; 4196 } else { 4197 Log.e(TAG, "Failed to get the app icon: " 4198 + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null"); 4199 } 4200 } else { 4201 Log.w(TAG, "Failed to get the app icon: no application info in extras"); 4202 } 4203 return null; 4204 } 4205 4206 /** 4207 * Whether the notification is using the app icon instead of the small icon. 4208 * @hide 4209 */ shouldUseAppIcon()4210 public boolean shouldUseAppIcon() { 4211 if (Flags.notificationsUseAppIconInRow()) { 4212 if (belongsToHeadlessSystemApp(/* context = */ null)) { 4213 return false; 4214 } 4215 return getAppIcon() != null; 4216 } 4217 return false; 4218 } 4219 4220 /** 4221 * The large icon shown in this notification's content view. 4222 * @see Builder#getLargeIcon() 4223 * @see Builder#setLargeIcon(Icon) 4224 */ getLargeIcon()4225 public Icon getLargeIcon() { 4226 return mLargeIcon; 4227 } 4228 4229 /** 4230 * @hide 4231 */ hasAppProvidedWhen()4232 public boolean hasAppProvidedWhen() { 4233 return when != 0 && when != creationTime; 4234 } 4235 4236 /** 4237 * @hide 4238 */ 4239 @UnsupportedAppUsage isGroupSummary()4240 public boolean isGroupSummary() { 4241 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) != 0; 4242 } 4243 4244 /** 4245 * @hide 4246 */ 4247 @UnsupportedAppUsage isGroupChild()4248 public boolean isGroupChild() { 4249 return mGroupKey != null && (flags & FLAG_GROUP_SUMMARY) == 0; 4250 } 4251 4252 /** 4253 * @hide 4254 */ suppressAlertingDueToGrouping()4255 public boolean suppressAlertingDueToGrouping() { 4256 if (isGroupSummary() 4257 && getGroupAlertBehavior() == Notification.GROUP_ALERT_CHILDREN) { 4258 return true; 4259 } else if (isGroupChild() 4260 && getGroupAlertBehavior() == Notification.GROUP_ALERT_SUMMARY) { 4261 return true; 4262 } 4263 return false; 4264 } 4265 4266 4267 /** 4268 * Finds and returns a remote input and its corresponding action. 4269 * 4270 * @param requiresFreeform requires the remoteinput to allow freeform or not. 4271 * @return the result pair, {@code null} if no result is found. 4272 */ 4273 @Nullable findRemoteInputActionPair(boolean requiresFreeform)4274 public Pair<RemoteInput, Action> findRemoteInputActionPair(boolean requiresFreeform) { 4275 if (actions == null) { 4276 return null; 4277 } 4278 for (Notification.Action action : actions) { 4279 if (action.getRemoteInputs() == null) { 4280 continue; 4281 } 4282 RemoteInput resultRemoteInput = null; 4283 for (RemoteInput remoteInput : action.getRemoteInputs()) { 4284 if (remoteInput.getAllowFreeFormInput() || !requiresFreeform) { 4285 resultRemoteInput = remoteInput; 4286 } 4287 } 4288 if (resultRemoteInput != null) { 4289 return Pair.create(resultRemoteInput, action); 4290 } 4291 } 4292 return null; 4293 } 4294 4295 /** 4296 * Returns the actions that are contextual (that is, suggested because of the content of the 4297 * notification) out of the actions in this notification. 4298 */ getContextualActions()4299 public @NonNull List<Notification.Action> getContextualActions() { 4300 if (actions == null) return Collections.emptyList(); 4301 4302 List<Notification.Action> contextualActions = new ArrayList<>(); 4303 for (Notification.Action action : actions) { 4304 if (action.isContextual()) { 4305 contextualActions.add(action); 4306 } 4307 } 4308 return contextualActions; 4309 } 4310 4311 /** 4312 * Builder class for {@link Notification} objects. 4313 * 4314 * Provides a convenient way to set the various fields of a {@link Notification} and generate 4315 * content views using the platform's notification layout template. If your app supports 4316 * versions of Android as old as API level 4, you can instead use 4317 * {@link androidx.core.app.NotificationCompat.Builder NotificationCompat.Builder}, 4318 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 4319 * library</a>. 4320 * 4321 * <p>Example: 4322 * 4323 * <pre class="prettyprint"> 4324 * Notification noti = new Notification.Builder(mContext) 4325 * .setContentTitle("New mail from " + sender.toString()) 4326 * .setContentText(subject) 4327 * .setSmallIcon(R.drawable.new_mail) 4328 * .setLargeIcon(aBitmap) 4329 * .build(); 4330 * </pre> 4331 */ 4332 public static class Builder { 4333 /** 4334 * @hide 4335 */ 4336 public static final String EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT = 4337 "android.rebuild.contentViewActionCount"; 4338 /** 4339 * @hide 4340 */ 4341 public static final String EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT 4342 = "android.rebuild.bigViewActionCount"; 4343 /** 4344 * @hide 4345 */ 4346 public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT 4347 = "android.rebuild.hudViewActionCount"; 4348 4349 private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY = 4350 SystemProperties.getBoolean("notifications.only_title", true); 4351 4352 /** 4353 * The lightness difference that has to be added to the primary text color to obtain the 4354 * secondary text color when the background is light. 4355 */ 4356 private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20; 4357 4358 /** 4359 * The lightness difference that has to be added to the primary text color to obtain the 4360 * secondary text color when the background is dark. 4361 * A bit less then the above value, since it looks better on dark backgrounds. 4362 */ 4363 private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10; 4364 4365 private Context mContext; 4366 private Notification mN; 4367 private Bundle mUserExtras = new Bundle(); 4368 private Style mStyle; 4369 @UnsupportedAppUsage 4370 private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS); 4371 private ArrayList<Person> mPersonList = new ArrayList<>(); 4372 private ContrastColorUtil mColorUtil; 4373 private boolean mIsLegacy; 4374 private boolean mIsLegacyInitialized; 4375 4376 /** 4377 * Caches an instance of StandardTemplateParams. Note that this may have been used before, 4378 * so make sure to call {@link StandardTemplateParams#reset()} before using it. 4379 */ 4380 StandardTemplateParams mParams = new StandardTemplateParams(); 4381 Colors mColors = new Colors(); 4382 4383 private boolean mTintActionButtons; 4384 private boolean mInNightMode; 4385 4386 /** 4387 * Constructs a new Builder with the defaults: 4388 * 4389 * @param context 4390 * A {@link Context} that will be used by the Builder to construct the 4391 * RemoteViews. The Context will not be held past the lifetime of this Builder 4392 * object. 4393 * @param channelId 4394 * The constructed Notification will be posted on this 4395 * {@link NotificationChannel}. To use a NotificationChannel, it must first be 4396 * created using {@link NotificationManager#createNotificationChannel}. 4397 */ Builder(Context context, String channelId)4398 public Builder(Context context, String channelId) { 4399 this(context, (Notification) null); 4400 mN.mChannelId = channelId; 4401 } 4402 4403 /** 4404 * @deprecated use {@link #Builder(Context, String)} 4405 * instead. All posted Notifications must specify a NotificationChannel Id. 4406 */ 4407 @Deprecated Builder(Context context)4408 public Builder(Context context) { 4409 this(context, (Notification) null); 4410 } 4411 4412 /** 4413 * @hide 4414 */ Builder(Context context, Notification toAdopt)4415 public Builder(Context context, Notification toAdopt) { 4416 mContext = context; 4417 Resources res = mContext.getResources(); 4418 mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons); 4419 4420 if (res.getBoolean(R.bool.config_enableNightMode)) { 4421 Configuration currentConfig = res.getConfiguration(); 4422 mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 4423 == Configuration.UI_MODE_NIGHT_YES; 4424 } 4425 4426 if (toAdopt == null) { 4427 mN = new Notification(); 4428 if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { 4429 mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); 4430 } 4431 mN.priority = PRIORITY_DEFAULT; 4432 mN.visibility = VISIBILITY_PRIVATE; 4433 } else { 4434 mN = toAdopt; 4435 if (mN.actions != null) { 4436 Collections.addAll(mActions, mN.actions); 4437 } 4438 4439 if (mN.extras.containsKey(EXTRA_PEOPLE_LIST)) { 4440 ArrayList<Person> people = mN.extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, 4441 android.app.Person.class); 4442 if (people != null && !people.isEmpty()) { 4443 mPersonList.addAll(people); 4444 } 4445 } 4446 4447 if (mN.getSmallIcon() == null && mN.icon != 0) { 4448 setSmallIcon(mN.icon); 4449 } 4450 4451 if (mN.getLargeIcon() == null && mN.largeIcon != null) { 4452 setLargeIcon(mN.largeIcon); 4453 } 4454 4455 String templateClass = mN.extras.getString(EXTRA_TEMPLATE); 4456 if (!TextUtils.isEmpty(templateClass)) { 4457 final Class<? extends Style> styleClass 4458 = getNotificationStyleClass(templateClass); 4459 if (styleClass == null) { 4460 Log.d(TAG, "Unknown style class: " + templateClass); 4461 } else { 4462 try { 4463 final Constructor<? extends Style> ctor = 4464 styleClass.getDeclaredConstructor(); 4465 ctor.setAccessible(true); 4466 final Style style = ctor.newInstance(); 4467 style.restoreFromExtras(mN.extras); 4468 4469 if (style != null) { 4470 setStyle(style); 4471 } 4472 } catch (Throwable t) { 4473 Log.e(TAG, "Could not create Style", t); 4474 } 4475 } 4476 } 4477 } 4478 } 4479 getColorUtil()4480 private ContrastColorUtil getColorUtil() { 4481 if (mColorUtil == null) { 4482 mColorUtil = ContrastColorUtil.getInstance(mContext); 4483 } 4484 return mColorUtil; 4485 } 4486 4487 /** 4488 * From Android 11, messaging notifications (those that use {@link MessagingStyle}) that 4489 * use this method to link to a published long-lived sharing shortcut may appear in a 4490 * dedicated Conversation section of the shade and may show configuration options that 4491 * are unique to conversations. This behavior should be reserved for person to person(s) 4492 * conversations where there is a likely social obligation for an individual to respond. 4493 * <p> 4494 * For example, the following are some examples of notifications that belong in the 4495 * conversation space: 4496 * <ul> 4497 * <li>1:1 conversations between two individuals</li> 4498 * <li>Group conversations between individuals where everyone can contribute</li> 4499 * </ul> 4500 * And the following are some examples of notifications that do not belong in the 4501 * conversation space: 4502 * <ul> 4503 * <li>Advertisements from a bot (even if personal and contextualized)</li> 4504 * <li>Engagement notifications from a bot</li> 4505 * <li>Directional conversations where there is an active speaker and many passive 4506 * individuals</li> 4507 * <li>Stream / posting updates from other individuals</li> 4508 * <li>Email, document comments, or other conversation types that are not real-time</li> 4509 * </ul> 4510 * </p> 4511 * 4512 * <p> 4513 * Additionally, this method can be used for all types of notifications to mark this 4514 * notification as duplicative of a Launcher shortcut. Launchers that show badges or 4515 * notification content may then suppress the shortcut in favor of the content of this 4516 * notification. 4517 * <p> 4518 * If this notification has {@link BubbleMetadata} attached that was created with 4519 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 4520 * metadata matches the shortcutId set here, if one was set. If the shortcutId's were 4521 * specified but do not match, an exception is thrown. 4522 * 4523 * @param shortcutId the {@link ShortcutInfo#getId() id} of the shortcut this notification 4524 * is linked to 4525 * 4526 * @see BubbleMetadata.Builder#Builder(String) 4527 */ 4528 @NonNull setShortcutId(String shortcutId)4529 public Builder setShortcutId(String shortcutId) { 4530 mN.mShortcutId = shortcutId; 4531 return this; 4532 } 4533 4534 /** 4535 * Sets the {@link LocusId} associated with this notification. 4536 * 4537 * <p>This method should be called when the {@link LocusId} is used in other places (such 4538 * as {@link ShortcutInfo} and {@link ContentCaptureContext}) so the device's intelligence 4539 * services can correlate them. 4540 */ 4541 @NonNull setLocusId(@ullable LocusId locusId)4542 public Builder setLocusId(@Nullable LocusId locusId) { 4543 mN.mLocusId = locusId; 4544 return this; 4545 } 4546 4547 /** 4548 * Sets which icon to display as a badge for this notification. 4549 * 4550 * Must be one of {@link #BADGE_ICON_NONE}, {@link #BADGE_ICON_SMALL}, 4551 * {@link #BADGE_ICON_LARGE}. 4552 * 4553 * Note: This value might be ignored, for launchers that don't support badge icons. 4554 */ 4555 @NonNull setBadgeIconType(int icon)4556 public Builder setBadgeIconType(int icon) { 4557 mN.mBadgeIcon = icon; 4558 return this; 4559 } 4560 4561 /** 4562 * Sets the group alert behavior for this notification. Use this method to mute this 4563 * notification if alerts for this notification's group should be handled by a different 4564 * notification. This is only applicable for notifications that belong to a 4565 * {@link #setGroup(String) group}. This must be called on all notifications you want to 4566 * mute. For example, if you want only the summary of your group to make noise and/or peek 4567 * on screen, all children in the group should have the group alert behavior 4568 * {@link #GROUP_ALERT_SUMMARY}. 4569 * 4570 * <p> The default value is {@link #GROUP_ALERT_ALL}.</p> 4571 */ 4572 @NonNull setGroupAlertBehavior(@roupAlertBehavior int groupAlertBehavior)4573 public Builder setGroupAlertBehavior(@GroupAlertBehavior int groupAlertBehavior) { 4574 mN.mGroupAlertBehavior = groupAlertBehavior; 4575 return this; 4576 } 4577 4578 /** 4579 * Sets the {@link BubbleMetadata} that will be used to display app content in a floating 4580 * window over the existing foreground activity. 4581 * 4582 * <p>This data will be ignored unless the notification is posted to a channel that 4583 * allows {@link NotificationChannel#canBubble() bubbles}.</p> 4584 * 4585 * <p>Notifications allowed to bubble that have valid bubble metadata will display in 4586 * collapsed state outside of the notification shade on unlocked devices. When a user 4587 * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p> 4588 */ 4589 @NonNull setBubbleMetadata(@ullable BubbleMetadata data)4590 public Builder setBubbleMetadata(@Nullable BubbleMetadata data) { 4591 mN.mBubbleMetadata = data; 4592 return this; 4593 } 4594 4595 /** @removed */ 4596 @Deprecated setChannel(String channelId)4597 public Builder setChannel(String channelId) { 4598 mN.mChannelId = channelId; 4599 return this; 4600 } 4601 4602 /** 4603 * Specifies the channel the notification should be delivered on. 4604 */ 4605 @NonNull setChannelId(String channelId)4606 public Builder setChannelId(String channelId) { 4607 mN.mChannelId = channelId; 4608 return this; 4609 } 4610 4611 /** @removed */ 4612 @Deprecated setTimeout(long durationMs)4613 public Builder setTimeout(long durationMs) { 4614 mN.mTimeout = durationMs; 4615 return this; 4616 } 4617 4618 /** 4619 * Specifies a duration in milliseconds after which this notification should be canceled, 4620 * if it is not already canceled. 4621 */ 4622 @NonNull setTimeoutAfter(long durationMs)4623 public Builder setTimeoutAfter(long durationMs) { 4624 mN.mTimeout = durationMs; 4625 return this; 4626 } 4627 4628 /** 4629 * Add a timestamp pertaining to the notification (usually the time the event occurred). 4630 * 4631 * @see Notification#when 4632 */ 4633 @NonNull setWhen(long when)4634 public Builder setWhen(long when) { 4635 mN.when = when; 4636 return this; 4637 } 4638 4639 /** 4640 * Control whether the timestamp set with {@link #setWhen(long) setWhen} is shown 4641 * in the content view. 4642 */ 4643 @NonNull setShowWhen(boolean show)4644 public Builder setShowWhen(boolean show) { 4645 mN.extras.putBoolean(EXTRA_SHOW_WHEN, show); 4646 return this; 4647 } 4648 4649 /** 4650 * Show the {@link Notification#when} field as a stopwatch. 4651 * 4652 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 4653 * automatically updating display of the minutes and seconds since <code>when</code>. 4654 * 4655 * Useful when showing an elapsed time (like an ongoing phone call). 4656 * 4657 * The counter can also be set to count down to <code>when</code> when using 4658 * {@link #setChronometerCountDown(boolean)}. 4659 * 4660 * @see android.widget.Chronometer 4661 * @see Notification#when 4662 * @see #setChronometerCountDown(boolean) 4663 */ 4664 @NonNull setUsesChronometer(boolean b)4665 public Builder setUsesChronometer(boolean b) { 4666 mN.extras.putBoolean(EXTRA_SHOW_CHRONOMETER, b); 4667 return this; 4668 } 4669 4670 /** 4671 * Sets the Chronometer to count down instead of counting up. 4672 * 4673 * <p>This is only relevant if {@link #setUsesChronometer(boolean)} has been set to true. 4674 * If it isn't set the chronometer will count up. 4675 * 4676 * @see #setUsesChronometer(boolean) 4677 */ 4678 @NonNull setChronometerCountDown(boolean countDown)4679 public Builder setChronometerCountDown(boolean countDown) { 4680 mN.extras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, countDown); 4681 return this; 4682 } 4683 4684 /** 4685 * Set the small icon resource, which will be used to represent the notification in the 4686 * status bar. 4687 * 4688 4689 * The platform template for the expanded view will draw this icon in the left, unless a 4690 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 4691 * icon will be moved to the right-hand side. 4692 * 4693 4694 * @param icon 4695 * A resource ID in the application's package of the drawable to use. 4696 * @see Notification#icon 4697 */ 4698 @NonNull setSmallIcon(@rawableRes int icon)4699 public Builder setSmallIcon(@DrawableRes int icon) { 4700 return setSmallIcon(icon != 0 4701 ? Icon.createWithResource(mContext, icon) 4702 : null); 4703 } 4704 4705 /** 4706 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 4707 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 4708 * LevelListDrawable}. 4709 * 4710 * @param icon A resource ID in the application's package of the drawable to use. 4711 * @param level The level to use for the icon. 4712 * 4713 * @see Notification#icon 4714 * @see Notification#iconLevel 4715 */ 4716 @NonNull setSmallIcon(@rawableRes int icon, int level)4717 public Builder setSmallIcon(@DrawableRes int icon, int level) { 4718 mN.iconLevel = level; 4719 return setSmallIcon(icon); 4720 } 4721 4722 /** 4723 * Set the small icon, which will be used to represent the notification in the 4724 * status bar and content view (unless overridden there by a 4725 * {@link #setLargeIcon(Bitmap) large icon}). 4726 * 4727 * @param icon An Icon object to use. 4728 * @see Notification#icon 4729 */ 4730 @NonNull setSmallIcon(Icon icon)4731 public Builder setSmallIcon(Icon icon) { 4732 mN.setSmallIcon(icon); 4733 if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { 4734 mN.icon = icon.getResId(); 4735 } 4736 return this; 4737 } 4738 4739 /** 4740 * If {@code true}, silences this instance of the notification, regardless of the sounds or 4741 * vibrations set on the notification or notification channel. If {@code false}, then the 4742 * normal sound and vibration logic applies. 4743 * 4744 * @hide 4745 */ setSilent(boolean silent)4746 public @NonNull Builder setSilent(boolean silent) { 4747 if (!silent) { 4748 return this; 4749 } 4750 if (mN.isGroupSummary()) { 4751 setGroupAlertBehavior(GROUP_ALERT_CHILDREN); 4752 } else { 4753 setGroupAlertBehavior(GROUP_ALERT_SUMMARY); 4754 } 4755 4756 setVibrate(null); 4757 setSound(null); 4758 mN.defaults &= ~DEFAULT_SOUND; 4759 mN.defaults &= ~DEFAULT_VIBRATE; 4760 setDefaults(mN.defaults); 4761 4762 if (TextUtils.isEmpty(mN.mGroupKey)) { 4763 setGroup(GROUP_KEY_SILENT); 4764 } 4765 return this; 4766 } 4767 4768 /** 4769 * Set the first line of text in the platform notification template. 4770 */ 4771 @NonNull setContentTitle(CharSequence title)4772 public Builder setContentTitle(CharSequence title) { 4773 mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); 4774 return this; 4775 } 4776 4777 /** 4778 * Set the second line of text in the platform notification template. 4779 */ 4780 @NonNull setContentText(CharSequence text)4781 public Builder setContentText(CharSequence text) { 4782 mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); 4783 return this; 4784 } 4785 4786 /** 4787 * This provides some additional information that is displayed in the notification. No 4788 * guarantees are given where exactly it is displayed. 4789 * 4790 * <p>This information should only be provided if it provides an essential 4791 * benefit to the understanding of the notification. The more text you provide the 4792 * less readable it becomes. For example, an email client should only provide the account 4793 * name here if more than one email account has been added.</p> 4794 * 4795 * <p>As of {@link android.os.Build.VERSION_CODES#N} this information is displayed in the 4796 * notification header area. 4797 * 4798 * On Android versions before {@link android.os.Build.VERSION_CODES#N} 4799 * this will be shown in the third line of text in the platform notification template. 4800 * You should not be using {@link #setProgress(int, int, boolean)} at the 4801 * same time on those versions; they occupy the same place. 4802 * </p> 4803 */ 4804 @NonNull setSubText(CharSequence text)4805 public Builder setSubText(CharSequence text) { 4806 mN.extras.putCharSequence(EXTRA_SUB_TEXT, safeCharSequence(text)); 4807 return this; 4808 } 4809 4810 /** 4811 * Provides text that will appear as a link to your application's settings. 4812 * 4813 * <p>This text does not appear within notification {@link Style templates} but may 4814 * appear when the user uses an affordance to learn more about the notification. 4815 * Additionally, this text will not appear unless you provide a valid link target by 4816 * handling {@link #INTENT_CATEGORY_NOTIFICATION_PREFERENCES}. 4817 * 4818 * <p>This text is meant to be concise description about what the user can customize 4819 * when they click on this link. The recommended maximum length is 40 characters. 4820 * @param text 4821 * @return 4822 */ 4823 @NonNull setSettingsText(CharSequence text)4824 public Builder setSettingsText(CharSequence text) { 4825 mN.mSettingsText = safeCharSequence(text); 4826 return this; 4827 } 4828 4829 /** 4830 * Set the remote input history. 4831 * 4832 * This should be set to the most recent inputs that have been sent 4833 * through a {@link RemoteInput} of this Notification and cleared once the it is no 4834 * longer relevant (e.g. for chat notifications once the other party has responded). 4835 * 4836 * The most recent input must be stored at the 0 index, the second most recent at the 4837 * 1 index, etc. Note that the system will limit both how far back the inputs will be shown 4838 * and how much of each individual input is shown. 4839 * 4840 * <p>Note: The reply text will only be shown on notifications that have least one action 4841 * with a {@code RemoteInput}.</p> 4842 */ 4843 @NonNull setRemoteInputHistory(CharSequence[] text)4844 public Builder setRemoteInputHistory(CharSequence[] text) { 4845 if (text == null) { 4846 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, null); 4847 } else { 4848 final int itemCount = Math.min(MAX_REPLY_HISTORY, text.length); 4849 CharSequence[] safe = new CharSequence[itemCount]; 4850 RemoteInputHistoryItem[] items = new RemoteInputHistoryItem[itemCount]; 4851 for (int i = 0; i < itemCount; i++) { 4852 safe[i] = safeCharSequence(text[i]); 4853 items[i] = new RemoteInputHistoryItem(text[i]); 4854 } 4855 mN.extras.putCharSequenceArray(EXTRA_REMOTE_INPUT_HISTORY, safe); 4856 4857 // Also add these messages as structured history items. 4858 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, items); 4859 } 4860 return this; 4861 } 4862 4863 /** 4864 * Set the remote input history, with support for embedding URIs and mime types for 4865 * images and other media. 4866 * @hide 4867 */ 4868 @NonNull setRemoteInputHistory(RemoteInputHistoryItem[] items)4869 public Builder setRemoteInputHistory(RemoteInputHistoryItem[] items) { 4870 if (items == null) { 4871 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, null); 4872 } else { 4873 final int itemCount = Math.min(MAX_REPLY_HISTORY, items.length); 4874 RemoteInputHistoryItem[] history = new RemoteInputHistoryItem[itemCount]; 4875 for (int i = 0; i < itemCount; i++) { 4876 history[i] = items[i]; 4877 } 4878 mN.extras.putParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS, history); 4879 } 4880 return this; 4881 } 4882 4883 /** 4884 * Sets whether remote history entries view should have a spinner. 4885 * @hide 4886 */ 4887 @NonNull setShowRemoteInputSpinner(boolean showSpinner)4888 public Builder setShowRemoteInputSpinner(boolean showSpinner) { 4889 mN.extras.putBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER, showSpinner); 4890 return this; 4891 } 4892 4893 /** 4894 * Sets whether smart reply buttons should be hidden. 4895 * @hide 4896 */ 4897 @NonNull setHideSmartReplies(boolean hideSmartReplies)4898 public Builder setHideSmartReplies(boolean hideSmartReplies) { 4899 mN.extras.putBoolean(EXTRA_HIDE_SMART_REPLIES, hideSmartReplies); 4900 return this; 4901 } 4902 4903 /** 4904 * Sets the number of items this notification represents. May be displayed as a badge count 4905 * for Launchers that support badging. 4906 */ 4907 @NonNull setNumber(int number)4908 public Builder setNumber(int number) { 4909 mN.number = number; 4910 return this; 4911 } 4912 4913 /** 4914 * A small piece of additional information pertaining to this notification. 4915 * 4916 * The platform template will draw this on the last line of the notification, at the far 4917 * right (to the right of a smallIcon if it has been placed there). 4918 * 4919 * @deprecated use {@link #setSubText(CharSequence)} instead to set a text in the header. 4920 * For legacy apps targeting a version below {@link android.os.Build.VERSION_CODES#N} this 4921 * field will still show up, but the subtext will take precedence. 4922 */ 4923 @Deprecated setContentInfo(CharSequence info)4924 public Builder setContentInfo(CharSequence info) { 4925 mN.extras.putCharSequence(EXTRA_INFO_TEXT, safeCharSequence(info)); 4926 return this; 4927 } 4928 4929 /** 4930 * Set the progress this notification represents. 4931 * 4932 * The platform template will represent this using a {@link ProgressBar}. 4933 */ 4934 @NonNull setProgress(int max, int progress, boolean indeterminate)4935 public Builder setProgress(int max, int progress, boolean indeterminate) { 4936 mN.extras.putInt(EXTRA_PROGRESS, progress); 4937 mN.extras.putInt(EXTRA_PROGRESS_MAX, max); 4938 mN.extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, indeterminate); 4939 return this; 4940 } 4941 4942 /** 4943 * Supply a custom RemoteViews to use instead of the platform template. 4944 * 4945 * Use {@link #setCustomContentView(RemoteViews)} instead. 4946 */ 4947 @Deprecated setContent(RemoteViews views)4948 public Builder setContent(RemoteViews views) { 4949 return setCustomContentView(views); 4950 } 4951 4952 /** 4953 * Supply custom RemoteViews to use instead of the platform template. 4954 * 4955 * This will override the layout that would otherwise be constructed by this Builder 4956 * object. 4957 */ 4958 @NonNull setCustomContentView(RemoteViews contentView)4959 public Builder setCustomContentView(RemoteViews contentView) { 4960 mN.contentView = contentView; 4961 return this; 4962 } 4963 4964 /** 4965 * Supply custom RemoteViews to use instead of the platform template in the expanded form. 4966 * 4967 * This will override the expanded layout that would otherwise be constructed by this 4968 * Builder object. 4969 */ 4970 @NonNull setCustomBigContentView(RemoteViews contentView)4971 public Builder setCustomBigContentView(RemoteViews contentView) { 4972 mN.bigContentView = contentView; 4973 return this; 4974 } 4975 4976 /** 4977 * Supply custom RemoteViews to use instead of the platform template in the heads up dialog. 4978 * 4979 * This will override the heads-up layout that would otherwise be constructed by this 4980 * Builder object. 4981 */ 4982 @NonNull setCustomHeadsUpContentView(RemoteViews contentView)4983 public Builder setCustomHeadsUpContentView(RemoteViews contentView) { 4984 mN.headsUpContentView = contentView; 4985 return this; 4986 } 4987 4988 /** 4989 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 4990 * 4991 * <p>As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 4992 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 4993 * while processing broadcast receivers or services in response to notification clicks. To 4994 * launch an activity in those cases, provide a {@link PendingIntent} for the activity 4995 * itself. 4996 * 4997 * <p>As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 4998 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 4999 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 5000 * to assign PendingIntents to individual views in that custom layout (i.e., to create 5001 * clickable buttons inside the notification view). 5002 * 5003 * @see Notification#contentIntent Notification.contentIntent 5004 */ 5005 @NonNull setContentIntent(PendingIntent intent)5006 public Builder setContentIntent(PendingIntent intent) { 5007 mN.contentIntent = intent; 5008 return this; 5009 } 5010 5011 /** 5012 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 5013 * 5014 * @see Notification#deleteIntent 5015 */ 5016 @NonNull setDeleteIntent(PendingIntent intent)5017 public Builder setDeleteIntent(PendingIntent intent) { 5018 mN.deleteIntent = intent; 5019 return this; 5020 } 5021 5022 /** 5023 * An intent to launch instead of posting the notification to the status bar. 5024 * Only for use with extremely high-priority notifications demanding the user's 5025 * <strong>immediate</strong> attention, such as an incoming phone call or 5026 * alarm clock that the user has explicitly set to a particular time. 5027 * If this facility is used for something else, please give the user an option 5028 * to turn it off and use a normal notification, as this can be extremely 5029 * disruptive. 5030 * 5031 * <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request 5032 * a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to 5033 * use full screen intents. </p> 5034 * <p> 5035 * Prior to {@link Build.VERSION_CODES#TIRAMISU}, the system may display a 5036 * heads up notification (which may display on screen longer than other heads up 5037 * notifications), instead of launching the intent, while the user is using the device. 5038 * From {@link Build.VERSION_CODES#TIRAMISU}, 5039 * the system UI will display a heads up notification, instead of launching this intent, 5040 * while the user is using the device. This notification will display with emphasized 5041 * action buttons. If the posting app holds 5042 * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the heads 5043 * up notification will appear persistently until the user dismisses or snoozes it, or 5044 * the app cancels it. If the posting app does not hold 5045 * {@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}, then the notification will 5046 * appear as heads up notification even when the screen is locked or turned off, and this 5047 * notification will only be persistent for 60 seconds. 5048 * </p> 5049 * <p> 5050 * To be launched as a full screen intent, the notification must also be posted to a 5051 * channel with importance level set to IMPORTANCE_HIGH or higher. 5052 * </p> 5053 * 5054 * @param intent The pending intent to launch. 5055 * @param highPriority Passing true will cause this notification to be sent 5056 * even if other notifications are suppressed. 5057 * 5058 * @see Notification#fullScreenIntent 5059 */ 5060 @NonNull setFullScreenIntent(PendingIntent intent, boolean highPriority)5061 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 5062 mN.fullScreenIntent = intent; 5063 setFlag(FLAG_HIGH_PRIORITY, highPriority); 5064 return this; 5065 } 5066 5067 /** 5068 * Set the "ticker" text which is sent to accessibility services. 5069 * 5070 * @see Notification#tickerText 5071 */ 5072 @NonNull setTicker(CharSequence tickerText)5073 public Builder setTicker(CharSequence tickerText) { 5074 mN.tickerText = safeCharSequence(tickerText); 5075 return this; 5076 } 5077 5078 /** 5079 * Obsolete version of {@link #setTicker(CharSequence)}. 5080 * 5081 */ 5082 @Deprecated setTicker(CharSequence tickerText, RemoteViews views)5083 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 5084 setTicker(tickerText); 5085 // views is ignored 5086 return this; 5087 } 5088 5089 /** 5090 * Add a large icon to the notification content view. 5091 * 5092 * In the platform template, this image will be shown either on the right of the 5093 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 5094 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 5095 */ 5096 @NonNull setLargeIcon(Bitmap b)5097 public Builder setLargeIcon(Bitmap b) { 5098 return setLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 5099 } 5100 5101 /** 5102 * Add a large icon to the notification content view. 5103 * 5104 * In the platform template, this image will be shown either on the right of the 5105 * notification, with an aspect ratio of up to 16:9, or (when the notification is grouped) 5106 * on the left in place of the {@link #setSmallIcon(Icon) small icon}. 5107 */ 5108 @NonNull setLargeIcon(Icon icon)5109 public Builder setLargeIcon(Icon icon) { 5110 mN.mLargeIcon = icon; 5111 mN.extras.putParcelable(EXTRA_LARGE_ICON, icon); 5112 return this; 5113 } 5114 5115 /** 5116 * Set the sound to play. 5117 * 5118 * It will be played using the {@link #AUDIO_ATTRIBUTES_DEFAULT default audio attributes} 5119 * for notifications. 5120 * 5121 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 5122 */ 5123 @Deprecated setSound(Uri sound)5124 public Builder setSound(Uri sound) { 5125 mN.sound = sound; 5126 mN.audioAttributes = AUDIO_ATTRIBUTES_DEFAULT; 5127 return this; 5128 } 5129 5130 /** 5131 * Set the sound to play, along with a specific stream on which to play it. 5132 * 5133 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 5134 * 5135 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)}. 5136 */ 5137 @Deprecated setSound(Uri sound, int streamType)5138 public Builder setSound(Uri sound, int streamType) { 5139 PlayerBase.deprecateStreamTypeForPlayback(streamType, "Notification", "setSound()"); 5140 mN.sound = sound; 5141 mN.audioStreamType = streamType; 5142 return this; 5143 } 5144 5145 /** 5146 * Set the sound to play, along with specific {@link AudioAttributes audio attributes} to 5147 * use during playback. 5148 * 5149 * @deprecated use {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 5150 * @see Notification#sound 5151 */ 5152 @Deprecated setSound(Uri sound, AudioAttributes audioAttributes)5153 public Builder setSound(Uri sound, AudioAttributes audioAttributes) { 5154 mN.sound = sound; 5155 mN.audioAttributes = audioAttributes; 5156 return this; 5157 } 5158 5159 /** 5160 * Set the vibration pattern to use. 5161 * 5162 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 5163 * <code>pattern</code> parameter. 5164 * 5165 * <p> 5166 * A notification that vibrates is more likely to be presented as a heads-up notification. 5167 * </p> 5168 * 5169 * @deprecated use {@link NotificationChannel#setVibrationPattern(long[])} instead. 5170 * @see Notification#vibrate 5171 */ 5172 @Deprecated setVibrate(long[] pattern)5173 public Builder setVibrate(long[] pattern) { 5174 mN.vibrate = pattern; 5175 return this; 5176 } 5177 5178 /** 5179 * Set the desired color for the indicator LED on the device, as well as the 5180 * blink duty cycle (specified in milliseconds). 5181 * 5182 5183 * Not all devices will honor all (or even any) of these values. 5184 * 5185 * @deprecated use {@link NotificationChannel#enableLights(boolean)} instead. 5186 * @see Notification#ledARGB 5187 * @see Notification#ledOnMS 5188 * @see Notification#ledOffMS 5189 */ 5190 @Deprecated setLights(@olorInt int argb, int onMs, int offMs)5191 public Builder setLights(@ColorInt int argb, int onMs, int offMs) { 5192 mN.ledARGB = argb; 5193 mN.ledOnMS = onMs; 5194 mN.ledOffMS = offMs; 5195 if (onMs != 0 || offMs != 0) { 5196 mN.flags |= FLAG_SHOW_LIGHTS; 5197 } 5198 return this; 5199 } 5200 5201 /** 5202 * Set whether this is an "ongoing" notification. 5203 * 5204 * Ongoing notifications cannot be dismissed by the user on locked devices, or by 5205 * notification listeners, and some notifications (call, device management, media) cannot 5206 * be dismissed on unlocked devices, so your application or service must take care of 5207 * canceling them. 5208 * 5209 * They are typically used to indicate a background task that the user is actively engaged 5210 * with (e.g., playing music) or is pending in some way and therefore occupying the device 5211 * (e.g., a file download, sync operation, active network connection). 5212 * 5213 * @see Notification#FLAG_ONGOING_EVENT 5214 */ 5215 @NonNull setOngoing(boolean ongoing)5216 public Builder setOngoing(boolean ongoing) { 5217 setFlag(FLAG_ONGOING_EVENT, ongoing); 5218 return this; 5219 } 5220 5221 /** 5222 * Set whether this notification should be colorized. When set, the color set with 5223 * {@link #setColor(int)} will be used as the background color of this notification. 5224 * <p> 5225 * This should only be used for high priority ongoing tasks like navigation, an ongoing 5226 * call, or other similarly high-priority events for the user. 5227 * <p> 5228 * For most styles, the coloring will only be applied if the notification is for a 5229 * foreground service notification. 5230 * However, for {@link MediaStyle} and {@link DecoratedMediaCustomViewStyle} notifications 5231 * that have a media session attached there is no such requirement. 5232 * 5233 * @see #setColor(int) 5234 * @see MediaStyle#setMediaSession(MediaSession.Token) 5235 */ 5236 @NonNull setColorized(boolean colorize)5237 public Builder setColorized(boolean colorize) { 5238 mN.extras.putBoolean(EXTRA_COLORIZED, colorize); 5239 return this; 5240 } 5241 5242 /** 5243 * Set this flag if you would only like the sound, vibrate 5244 * and ticker to be played if the notification is not already showing. 5245 * 5246 * Note that using this flag will stop any ongoing alerting behaviour such 5247 * as sound, vibration or blinking notification LED. 5248 * 5249 * @see Notification#FLAG_ONLY_ALERT_ONCE 5250 */ 5251 @NonNull setOnlyAlertOnce(boolean onlyAlertOnce)5252 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 5253 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 5254 return this; 5255 } 5256 5257 /** 5258 * Specify a desired visibility policy for a Notification associated with a 5259 * foreground service. By default, the system can choose to defer 5260 * visibility of the notification for a short time after the service is 5261 * started. Pass 5262 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE} 5263 * to this method in order to guarantee that visibility is never deferred. Pass 5264 * {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 5265 * to request that visibility is deferred whenever possible. 5266 * 5267 * <p class="note">Note that deferred visibility is not guaranteed. There 5268 * may be some circumstances under which the system will show the foreground 5269 * service's associated Notification immediately even when the app has used 5270 * this method to explicitly request deferred display.</p> 5271 * @param behavior One of 5272 * {@link Notification#FOREGROUND_SERVICE_DEFAULT FOREGROUND_SERVICE_DEFAULT}, 5273 * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE FOREGROUND_SERVICE_IMMEDIATE}, 5274 * or {@link Notification#FOREGROUND_SERVICE_DEFERRED FOREGROUND_SERVICE_DEFERRED} 5275 * @return 5276 */ 5277 @NonNull setForegroundServiceBehavior(@erviceNotificationPolicy int behavior)5278 public Builder setForegroundServiceBehavior(@ServiceNotificationPolicy int behavior) { 5279 mN.mFgsDeferBehavior = behavior; 5280 return this; 5281 } 5282 5283 /** 5284 * Make this notification automatically dismissed when the user touches it. 5285 * 5286 * @see Notification#FLAG_AUTO_CANCEL 5287 */ 5288 @NonNull setAutoCancel(boolean autoCancel)5289 public Builder setAutoCancel(boolean autoCancel) { 5290 setFlag(FLAG_AUTO_CANCEL, autoCancel); 5291 return this; 5292 } 5293 5294 /** 5295 * Set whether or not this notification should not bridge to other devices. 5296 * 5297 * <p>Some notifications can be bridged to other devices for remote display. 5298 * This hint can be set to recommend this notification not be bridged. 5299 */ 5300 @NonNull setLocalOnly(boolean localOnly)5301 public Builder setLocalOnly(boolean localOnly) { 5302 setFlag(FLAG_LOCAL_ONLY, localOnly); 5303 return this; 5304 } 5305 5306 /** 5307 * Set which notification properties will be inherited from system defaults. 5308 * <p> 5309 * The value should be one or more of the following fields combined with 5310 * bitwise-or: 5311 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 5312 * <p> 5313 * For all default values, use {@link #DEFAULT_ALL}. 5314 * 5315 * @deprecated use {@link NotificationChannel#enableVibration(boolean)} and 5316 * {@link NotificationChannel#enableLights(boolean)} and 5317 * {@link NotificationChannel#setSound(Uri, AudioAttributes)} instead. 5318 */ 5319 @Deprecated setDefaults(int defaults)5320 public Builder setDefaults(int defaults) { 5321 mN.defaults = defaults; 5322 return this; 5323 } 5324 5325 /** 5326 * Set the priority of this notification. 5327 * 5328 * @see Notification#priority 5329 * @deprecated use {@link NotificationChannel#setImportance(int)} instead. 5330 */ 5331 @Deprecated setPriority(@riority int pri)5332 public Builder setPriority(@Priority int pri) { 5333 mN.priority = pri; 5334 return this; 5335 } 5336 5337 /** 5338 * Set the notification category. 5339 * 5340 * @see Notification#category 5341 */ 5342 @NonNull setCategory(String category)5343 public Builder setCategory(String category) { 5344 mN.category = category; 5345 return this; 5346 } 5347 5348 /** 5349 * Add a person that is relevant to this notification. 5350 * 5351 * <P> 5352 * Depending on user preferences, this annotation may allow the notification to pass 5353 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 5354 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 5355 * appear more prominently in the user interface. 5356 * </P> 5357 * 5358 * <P> 5359 * The person should be specified by the {@code String} representation of a 5360 * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 5361 * </P> 5362 * 5363 * <P>The system will also attempt to resolve {@code mailto:} and {@code tel:} schema 5364 * URIs. The path part of these URIs must exist in the contacts database, in the 5365 * appropriate column, or the reference will be discarded as invalid. Telephone schema 5366 * URIs will be resolved by {@link android.provider.ContactsContract.PhoneLookup}. 5367 * It is also possible to provide a URI with the schema {@code name:} in order to uniquely 5368 * identify a person without an entry in the contacts database. 5369 * </P> 5370 * 5371 * @param uri A URI for the person. 5372 * @see Notification#EXTRA_PEOPLE 5373 * @deprecated use {@link #addPerson(Person)} 5374 */ addPerson(String uri)5375 public Builder addPerson(String uri) { 5376 addPerson(new Person.Builder().setUri(uri).build()); 5377 return this; 5378 } 5379 5380 /** 5381 * Add a person that is relevant to this notification. 5382 * 5383 * <P> 5384 * Depending on user preferences, this annotation may allow the notification to pass 5385 * through interruption filters, if this notification is of category {@link #CATEGORY_CALL} 5386 * or {@link #CATEGORY_MESSAGE}. The addition of people may also cause this notification to 5387 * appear more prominently in the user interface. 5388 * </P> 5389 * 5390 * <P> 5391 * A person should usually contain a uri in order to benefit from the ranking boost. 5392 * However, even if no uri is provided, it's beneficial to provide other people in the 5393 * notification, such that listeners and voice only devices can announce and handle them 5394 * properly. 5395 * </P> 5396 * 5397 * @param person the person to add. 5398 * @see Notification#EXTRA_PEOPLE_LIST 5399 */ 5400 @NonNull addPerson(Person person)5401 public Builder addPerson(Person person) { 5402 mPersonList.add(person); 5403 return this; 5404 } 5405 5406 /** 5407 * Set this notification to be part of a group of notifications sharing the same key. 5408 * Grouped notifications may display in a cluster or stack on devices which 5409 * support such rendering. 5410 * 5411 * <p>To make this notification the summary for its group, also call 5412 * {@link #setGroupSummary}. A sort order can be specified for group members by using 5413 * {@link #setSortKey}. 5414 * @param groupKey The group key of the group. 5415 * @return this object for method chaining 5416 */ 5417 @NonNull setGroup(String groupKey)5418 public Builder setGroup(String groupKey) { 5419 mN.mGroupKey = groupKey; 5420 return this; 5421 } 5422 5423 /** 5424 * Set this notification to be the group summary for a group of notifications. 5425 * Grouped notifications may display in a cluster or stack on devices which 5426 * support such rendering. If thereRequires a group key also be set using {@link #setGroup}. 5427 * The group summary may be suppressed if too few notifications are included in the group. 5428 * @param isGroupSummary Whether this notification should be a group summary. 5429 * @return this object for method chaining 5430 */ 5431 @NonNull setGroupSummary(boolean isGroupSummary)5432 public Builder setGroupSummary(boolean isGroupSummary) { 5433 setFlag(FLAG_GROUP_SUMMARY, isGroupSummary); 5434 return this; 5435 } 5436 5437 /** 5438 * Set a sort key that orders this notification among other notifications from the 5439 * same package. This can be useful if an external sort was already applied and an app 5440 * would like to preserve this. Notifications will be sorted lexicographically using this 5441 * value, although providing different priorities in addition to providing sort key may 5442 * cause this value to be ignored. 5443 * 5444 * <p>This sort key can also be used to order members of a notification group. See 5445 * {@link #setGroup}. 5446 * 5447 * @see String#compareTo(String) 5448 */ 5449 @NonNull setSortKey(String sortKey)5450 public Builder setSortKey(String sortKey) { 5451 mN.mSortKey = sortKey; 5452 return this; 5453 } 5454 5455 /** 5456 * Merge additional metadata into this notification. 5457 * 5458 * <p>Values within the Bundle will replace existing extras values in this Builder. 5459 * 5460 * @see Notification#extras 5461 */ 5462 @NonNull addExtras(Bundle extras)5463 public Builder addExtras(Bundle extras) { 5464 if (extras != null) { 5465 mUserExtras.putAll(extras); 5466 } 5467 return this; 5468 } 5469 5470 /** 5471 * Set metadata for this notification. 5472 * 5473 * <p>A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 5474 * current contents are copied into the Notification each time {@link #build()} is 5475 * called. 5476 * 5477 * <p>Replaces any existing extras values with those from the provided Bundle. 5478 * Use {@link #addExtras} to merge in metadata instead. 5479 * 5480 * @see Notification#extras 5481 */ 5482 @NonNull setExtras(Bundle extras)5483 public Builder setExtras(Bundle extras) { 5484 if (extras != null) { 5485 mUserExtras = extras; 5486 } 5487 return this; 5488 } 5489 5490 /** 5491 * Get the current metadata Bundle used by this notification Builder. 5492 * 5493 * <p>The returned Bundle is shared with this Builder. 5494 * 5495 * <p>The current contents of this Bundle are copied into the Notification each time 5496 * {@link #build()} is called. 5497 * 5498 * @see Notification#extras 5499 */ getExtras()5500 public Bundle getExtras() { 5501 return mUserExtras; 5502 } 5503 5504 /** 5505 * Add an action to this notification. Actions are typically displayed by 5506 * the system as a button adjacent to the notification content. 5507 * <p> 5508 * Every action must have an icon (32dp square and matching the 5509 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 5510 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 5511 * <p> 5512 * A notification in its expanded form can display up to 3 actions, from left to right in 5513 * the order they were added. Actions will not be displayed when the notification is 5514 * collapsed, however, so be sure that any essential functions may be accessed by the user 5515 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 5516 * <p> 5517 * As of Android {@link android.os.Build.VERSION_CODES#S}, apps targeting API level 5518 * {@link android.os.Build.VERSION_CODES#S} or higher won't be able to start activities 5519 * while processing broadcast receivers or services in response to notification action 5520 * clicks. To launch an activity in those cases, provide a {@link PendingIntent} to the 5521 * activity itself. 5522 * <p> 5523 * As of Android {@link android.os.Build.VERSION_CODES#N}, 5524 * action button icons will not be displayed on action buttons, but are still required 5525 * and are available to 5526 * {@link android.service.notification.NotificationListenerService notification listeners}, 5527 * which may display them in other contexts, for example on a wearable device. 5528 * 5529 * @param icon Resource ID of a drawable that represents the action. 5530 * @param title Text describing the action. 5531 * @param intent PendingIntent to be fired when the action is invoked. 5532 * 5533 * @deprecated Use {@link #addAction(Action)} instead. 5534 */ 5535 @Deprecated addAction(int icon, CharSequence title, PendingIntent intent)5536 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 5537 mActions.add(new Action(icon, safeCharSequence(title), intent)); 5538 return this; 5539 } 5540 5541 /** 5542 * Add an action to this notification. Actions are typically displayed by 5543 * the system as a button adjacent to the notification content. 5544 * <p> 5545 * Every action must have an icon (32dp square and matching the 5546 * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo 5547 * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}. 5548 * <p> 5549 * A notification in its expanded form can display up to 3 actions, from left to right in 5550 * the order they were added. Actions will not be displayed when the notification is 5551 * collapsed, however, so be sure that any essential functions may be accessed by the user 5552 * in some other way (for example, in the Activity pointed to by {@link #contentIntent}). 5553 * 5554 * @param action The action to add. 5555 */ 5556 @NonNull addAction(Action action)5557 public Builder addAction(Action action) { 5558 if (action != null) { 5559 mActions.add(action); 5560 } 5561 return this; 5562 } 5563 5564 /** 5565 * Alter the complete list of actions attached to this notification. 5566 * @see #addAction(Action). 5567 * 5568 * @param actions 5569 * @return 5570 */ 5571 @NonNull setActions(Action... actions)5572 public Builder setActions(Action... actions) { 5573 mActions.clear(); 5574 for (int i = 0; i < actions.length; i++) { 5575 if (actions[i] != null) { 5576 mActions.add(actions[i]); 5577 } 5578 } 5579 return this; 5580 } 5581 5582 /** 5583 * Add a rich notification style to be applied at build time. 5584 * 5585 * @param style Object responsible for modifying the notification style. 5586 */ 5587 @NonNull setStyle(Style style)5588 public Builder setStyle(Style style) { 5589 if (mStyle != style) { 5590 mStyle = style; 5591 if (mStyle != null) { 5592 mStyle.setBuilder(this); 5593 mN.extras.putString(EXTRA_TEMPLATE, style.getClass().getName()); 5594 } else { 5595 mN.extras.remove(EXTRA_TEMPLATE); 5596 } 5597 } 5598 return this; 5599 } 5600 5601 /** 5602 * Returns the style set by {@link #setStyle(Style)}. 5603 */ getStyle()5604 public Style getStyle() { 5605 return mStyle; 5606 } 5607 5608 /** 5609 * Specify the value of {@link #visibility}. 5610 * 5611 * @return The same Builder. 5612 */ 5613 @NonNull setVisibility(@isibility int visibility)5614 public Builder setVisibility(@Visibility int visibility) { 5615 mN.visibility = visibility; 5616 return this; 5617 } 5618 5619 /** 5620 * Supply a replacement Notification whose contents should be shown in insecure contexts 5621 * (i.e. atop the secure lockscreen). See {@link #visibility} and {@link #VISIBILITY_PUBLIC}. 5622 * @param n A replacement notification, presumably with some or all info redacted. 5623 * @return The same Builder. 5624 */ 5625 @NonNull setPublicVersion(Notification n)5626 public Builder setPublicVersion(Notification n) { 5627 if (n != null) { 5628 mN.publicVersion = new Notification(); 5629 n.cloneInto(mN.publicVersion, /*heavy=*/ true); 5630 } else { 5631 mN.publicVersion = null; 5632 } 5633 return this; 5634 } 5635 5636 /** 5637 * Apply an extender to this notification builder. Extenders may be used to add 5638 * metadata or change options on this builder. 5639 */ 5640 @NonNull extend(Extender extender)5641 public Builder extend(Extender extender) { 5642 extender.extend(this); 5643 return this; 5644 } 5645 5646 /** 5647 * Set the value for a notification flag 5648 * 5649 * @param mask Bit mask of the flag 5650 * @param value Status (on/off) of the flag 5651 * 5652 * @return The same Builder. 5653 */ 5654 @NonNull setFlag(@otificationFlags int mask, boolean value)5655 public Builder setFlag(@NotificationFlags int mask, boolean value) { 5656 if (value) { 5657 mN.flags |= mask; 5658 } else { 5659 mN.flags &= ~mask; 5660 } 5661 return this; 5662 } 5663 5664 /** 5665 * Sets {@link Notification#color}. 5666 * 5667 * @param argb The accent color to use 5668 * 5669 * @return The same Builder. 5670 */ 5671 @NonNull setColor(@olorInt int argb)5672 public Builder setColor(@ColorInt int argb) { 5673 mN.color = argb; 5674 sanitizeColor(); 5675 return this; 5676 } 5677 bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p)5678 private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) { 5679 contentView.setDrawableTint( 5680 R.id.phishing_alert, 5681 false /* targetBackground */, 5682 getColors(p).getErrorColor(), 5683 PorterDuff.Mode.SRC_ATOP); 5684 } 5685 getProfileBadgeDrawable()5686 private Drawable getProfileBadgeDrawable() { 5687 if (mContext.getUserId() == UserHandle.USER_SYSTEM) { 5688 // This user can never be a badged profile, 5689 // and also includes USER_ALL system notifications. 5690 return null; 5691 } 5692 // Note: This assumes that the current user can read the profile badge of the 5693 // originating user. 5694 DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); 5695 return dpm.getResources().getDrawable( 5696 getUpdatableProfileBadgeId(), SOLID_COLORED, NOTIFICATION, 5697 this::getDefaultProfileBadgeDrawable); 5698 } 5699 getUpdatableProfileBadgeId()5700 private String getUpdatableProfileBadgeId() { 5701 return mContext.getSystemService(UserManager.class).isManagedProfile() 5702 ? WORK_PROFILE_ICON : UNDEFINED; 5703 } 5704 getDefaultProfileBadgeDrawable()5705 private Drawable getDefaultProfileBadgeDrawable() { 5706 return mContext.getPackageManager().getUserBadgeForDensityNoBackground( 5707 new UserHandle(mContext.getUserId()), 0); 5708 } 5709 getProfileBadge()5710 private Bitmap getProfileBadge() { 5711 Drawable badge = getProfileBadgeDrawable(); 5712 if (badge == null) { 5713 return null; 5714 } 5715 final int size = mContext.getResources().getDimensionPixelSize( 5716 R.dimen.notification_badge_size); 5717 Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); 5718 Canvas canvas = new Canvas(bitmap); 5719 badge.setBounds(0, 0, size, size); 5720 badge.draw(canvas); 5721 return bitmap; 5722 } 5723 bindProfileBadge(RemoteViews contentView, StandardTemplateParams p)5724 private void bindProfileBadge(RemoteViews contentView, StandardTemplateParams p) { 5725 Bitmap profileBadge = getProfileBadge(); 5726 5727 if (profileBadge != null) { 5728 contentView.setImageViewBitmap(R.id.profile_badge, profileBadge); 5729 contentView.setViewVisibility(R.id.profile_badge, View.VISIBLE); 5730 if (isBackgroundColorized(p)) { 5731 contentView.setDrawableTint(R.id.profile_badge, false, 5732 getPrimaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 5733 } 5734 contentView.setContentDescription( 5735 R.id.profile_badge, 5736 mContext.getSystemService(UserManager.class) 5737 .getProfileAccessibilityString(mContext.getUserId())); 5738 } 5739 } 5740 bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p)5741 private void bindAlertedIcon(RemoteViews contentView, StandardTemplateParams p) { 5742 contentView.setDrawableTint( 5743 R.id.alerted_icon, 5744 false /* targetBackground */, 5745 getColors(p).getSecondaryTextColor(), 5746 PorterDuff.Mode.SRC_IN); 5747 } 5748 5749 /** 5750 * @hide 5751 */ usesStandardHeader()5752 public boolean usesStandardHeader() { 5753 if (mN.mUsesStandardHeader) { 5754 return true; 5755 } 5756 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { 5757 if (mN.contentView == null && mN.bigContentView == null) { 5758 return true; 5759 } 5760 } 5761 boolean contentViewUsesHeader = mN.contentView == null 5762 || STANDARD_LAYOUTS.contains(mN.contentView.getLayoutId()); 5763 boolean bigContentViewUsesHeader = mN.bigContentView == null 5764 || STANDARD_LAYOUTS.contains(mN.bigContentView.getLayoutId()); 5765 return contentViewUsesHeader && bigContentViewUsesHeader; 5766 } 5767 resetStandardTemplate(RemoteViews contentView)5768 private void resetStandardTemplate(RemoteViews contentView) { 5769 resetNotificationHeader(contentView); 5770 contentView.setViewVisibility(R.id.right_icon, View.GONE); 5771 contentView.setViewVisibility(R.id.title, View.GONE); 5772 contentView.setTextViewText(R.id.title, null); 5773 contentView.setViewVisibility(R.id.text, View.GONE); 5774 contentView.setTextViewText(R.id.text, null); 5775 } 5776 5777 /** 5778 * Resets the notification header to its original state 5779 */ resetNotificationHeader(RemoteViews contentView)5780 private void resetNotificationHeader(RemoteViews contentView) { 5781 // Small icon doesn't need to be reset, as it's always set. Resetting would prevent 5782 // re-using the drawable when the notification is updated. 5783 contentView.setBoolean(R.id.expand_button, "setExpanded", false); 5784 contentView.setViewVisibility(R.id.app_name_text, View.GONE); 5785 contentView.setTextViewText(R.id.app_name_text, null); 5786 contentView.setViewVisibility(R.id.chronometer, View.GONE); 5787 contentView.setViewVisibility(R.id.header_text, View.GONE); 5788 contentView.setTextViewText(R.id.header_text, null); 5789 contentView.setViewVisibility(R.id.header_text_secondary, View.GONE); 5790 contentView.setTextViewText(R.id.header_text_secondary, null); 5791 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 5792 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 5793 contentView.setViewVisibility(R.id.time_divider, View.GONE); 5794 contentView.setViewVisibility(R.id.time, View.GONE); 5795 contentView.setImageViewIcon(R.id.profile_badge, null); 5796 contentView.setViewVisibility(R.id.profile_badge, View.GONE); 5797 mN.mUsesStandardHeader = false; 5798 } 5799 applyStandardTemplate(int resId, StandardTemplateParams p, TemplateBindResult result)5800 private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p, 5801 TemplateBindResult result) { 5802 p.headerless(resId == getBaseLayoutResource() 5803 || resId == getHeadsUpBaseLayoutResource() 5804 || resId == getCompactHeadsUpBaseLayoutResource() 5805 || resId == getMessagingCompactHeadsUpLayoutResource() 5806 || resId == getMessagingLayoutResource() 5807 || resId == R.layout.notification_template_material_media); 5808 RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); 5809 5810 resetStandardTemplate(contentView); 5811 5812 final Bundle ex = mN.extras; 5813 updateBackgroundColor(contentView, p); 5814 bindNotificationHeader(contentView, p); 5815 bindLargeIconAndApplyMargin(contentView, p, result); 5816 boolean showProgress = handleProgressBar(contentView, ex, p); 5817 boolean hasSecondLine = showProgress; 5818 if (p.hasTitle()) { 5819 contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE); 5820 contentView.setTextViewText(p.mTitleViewId, 5821 ensureColorSpanContrastOrStripStyling(p.mTitle, p)); 5822 setTextViewColorPrimary(contentView, p.mTitleViewId, p); 5823 } else if (p.mTitleViewId != R.id.title) { 5824 // This alternate title view ID is not cleared by resetStandardTemplate 5825 contentView.setViewVisibility(p.mTitleViewId, View.GONE); 5826 contentView.setTextViewText(p.mTitleViewId, null); 5827 } 5828 if (p.mText != null && p.mText.length() != 0 5829 && (!showProgress || p.mAllowTextWithProgress)) { 5830 contentView.setViewVisibility(p.mTextViewId, View.VISIBLE); 5831 contentView.setTextViewText(p.mTextViewId, 5832 ensureColorSpanContrastOrStripStyling(p.mText, p)); 5833 setTextViewColorSecondary(contentView, p.mTextViewId, p); 5834 hasSecondLine = true; 5835 } else if (p.mTextViewId != R.id.text) { 5836 // This alternate text view ID is not cleared by resetStandardTemplate 5837 contentView.setViewVisibility(p.mTextViewId, View.GONE); 5838 contentView.setTextViewText(p.mTextViewId, null); 5839 } 5840 setHeaderlessVerticalMargins(contentView, p, hasSecondLine); 5841 5842 return contentView; 5843 } 5844 setHeaderlessVerticalMargins(RemoteViews contentView, StandardTemplateParams p, boolean hasSecondLine)5845 private static void setHeaderlessVerticalMargins(RemoteViews contentView, 5846 StandardTemplateParams p, boolean hasSecondLine) { 5847 if (!p.mHeaderless) { 5848 return; 5849 } 5850 int marginDimen = hasSecondLine 5851 ? R.dimen.notification_headerless_margin_twoline 5852 : R.dimen.notification_headerless_margin_oneline; 5853 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5854 RemoteViews.MARGIN_TOP, marginDimen); 5855 contentView.setViewLayoutMarginDimen(R.id.notification_headerless_view_column, 5856 RemoteViews.MARGIN_BOTTOM, marginDimen); 5857 } 5858 setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5859 private void setTextViewColorPrimary(RemoteViews contentView, @IdRes int id, 5860 StandardTemplateParams p) { 5861 contentView.setTextColor(id, getPrimaryTextColor(p)); 5862 } 5863 5864 /** 5865 * @param p the template params to inflate this with 5866 * @return the primary text color 5867 * @hide 5868 */ 5869 @VisibleForTesting getPrimaryTextColor(StandardTemplateParams p)5870 public @ColorInt int getPrimaryTextColor(StandardTemplateParams p) { 5871 return getColors(p).getPrimaryTextColor(); 5872 } 5873 5874 /** 5875 * @param p the template params to inflate this with 5876 * @return the secondary text color 5877 * @hide 5878 */ 5879 @VisibleForTesting getSecondaryTextColor(StandardTemplateParams p)5880 public @ColorInt int getSecondaryTextColor(StandardTemplateParams p) { 5881 return getColors(p).getSecondaryTextColor(); 5882 } 5883 setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, StandardTemplateParams p)5884 private void setTextViewColorSecondary(RemoteViews contentView, @IdRes int id, 5885 StandardTemplateParams p) { 5886 contentView.setTextColor(id, getSecondaryTextColor(p)); 5887 } 5888 getColors(StandardTemplateParams p)5889 private Colors getColors(StandardTemplateParams p) { 5890 mColors.resolvePalette(mContext, mN.color, isBackgroundColorized(p), mInNightMode); 5891 return mColors; 5892 } 5893 5894 /** 5895 * @param isHeader If the notification is a notification header 5896 * @return An instance of mColors after resolving the palette 5897 */ getColors(boolean isHeader)5898 private Colors getColors(boolean isHeader) { 5899 mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode); 5900 return mColors; 5901 } 5902 updateBackgroundColor(RemoteViews contentView, StandardTemplateParams p)5903 private void updateBackgroundColor(RemoteViews contentView, 5904 StandardTemplateParams p) { 5905 if (isBackgroundColorized(p)) { 5906 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundColor", 5907 getBackgroundColor(p)); 5908 } else { 5909 // Clear it! 5910 contentView.setInt(R.id.status_bar_latest_event_content, "setBackgroundResource", 5911 0); 5912 } 5913 } 5914 handleProgressBar(RemoteViews contentView, Bundle ex, StandardTemplateParams p)5915 private boolean handleProgressBar(RemoteViews contentView, Bundle ex, 5916 StandardTemplateParams p) { 5917 final int max = ex.getInt(EXTRA_PROGRESS_MAX, 0); 5918 final int progress = ex.getInt(EXTRA_PROGRESS, 0); 5919 final boolean ind = ex.getBoolean(EXTRA_PROGRESS_INDETERMINATE); 5920 if (!p.mHideProgress && (max != 0 || ind)) { 5921 contentView.setViewVisibility(com.android.internal.R.id.progress, View.VISIBLE); 5922 contentView.setProgressBar(R.id.progress, max, progress, ind); 5923 contentView.setProgressBackgroundTintList(R.id.progress, 5924 mContext.getColorStateList(R.color.notification_progress_background_color)); 5925 ColorStateList progressTint = ColorStateList.valueOf(getPrimaryAccentColor(p)); 5926 contentView.setProgressTintList(R.id.progress, progressTint); 5927 contentView.setProgressIndeterminateTintList(R.id.progress, progressTint); 5928 return true; 5929 } else { 5930 contentView.setViewVisibility(R.id.progress, View.GONE); 5931 return false; 5932 } 5933 } 5934 bindLargeIconAndApplyMargin(RemoteViews contentView, @NonNull StandardTemplateParams p, @Nullable TemplateBindResult result)5935 private void bindLargeIconAndApplyMargin(RemoteViews contentView, 5936 @NonNull StandardTemplateParams p, 5937 @Nullable TemplateBindResult result) { 5938 if (result == null) { 5939 result = new TemplateBindResult(); 5940 } 5941 bindLargeIcon(contentView, p, result); 5942 if (!p.mHeaderless) { 5943 // views in states with a header (big states) 5944 result.mHeadingExtraMarginSet.applyToView(contentView, R.id.notification_header); 5945 result.mTitleMarginSet.applyToView(contentView, R.id.title); 5946 // If there is no title, the text (or big_text) needs to wrap around the image 5947 result.mTitleMarginSet.applyToView(contentView, p.mTextViewId); 5948 contentView.setInt(p.mTextViewId, "setNumIndentLines", p.hasTitle() ? 0 : 1); 5949 } 5950 } 5951 5952 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 5953 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 5954 // the change's state in NotificationManagerService were very complex. These behavior 5955 // changes are entirely visual, and should otherwise be undetectable by apps. 5956 @SuppressWarnings("AndroidFrameworkCompatChange") calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, @NonNull TemplateBindResult result)5957 private void calculateRightIconDimens(Icon rightIcon, boolean isPromotedPicture, 5958 @NonNull TemplateBindResult result) { 5959 final Resources resources = mContext.getResources(); 5960 final float density = resources.getDisplayMetrics().density; 5961 final float iconMarginDp = resources.getDimension( 5962 R.dimen.notification_right_icon_content_margin) / density; 5963 final float contentMarginDp = resources.getDimension( 5964 R.dimen.notification_content_margin_end) / density; 5965 final float expanderSizeDp = resources.getDimension( 5966 R.dimen.notification_header_expand_icon_size) / density - contentMarginDp; 5967 final float viewHeightDp = resources.getDimension( 5968 R.dimen.notification_right_icon_size) / density; 5969 float viewWidthDp = viewHeightDp; // icons are 1:1 by default 5970 if (rightIcon != null && (isPromotedPicture 5971 || mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S)) { 5972 Drawable drawable = rightIcon.loadDrawable(mContext); 5973 if (drawable != null) { 5974 int iconWidth = drawable.getIntrinsicWidth(); 5975 int iconHeight = drawable.getIntrinsicHeight(); 5976 if (iconWidth > iconHeight && iconHeight > 0) { 5977 final float maxViewWidthDp = viewHeightDp * MAX_LARGE_ICON_ASPECT_RATIO; 5978 viewWidthDp = Math.min(viewHeightDp * iconWidth / iconHeight, 5979 maxViewWidthDp); 5980 } 5981 } 5982 } 5983 final float extraMarginEndDpIfVisible = viewWidthDp + iconMarginDp; 5984 result.setRightIconState(rightIcon != null /* visible */, viewWidthDp, 5985 viewHeightDp, extraMarginEndDpIfVisible, expanderSizeDp); 5986 } 5987 5988 /** 5989 * Bind the large icon. 5990 */ bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)5991 private void bindLargeIcon(RemoteViews contentView, @NonNull StandardTemplateParams p, 5992 @NonNull TemplateBindResult result) { 5993 if (mN.mLargeIcon == null && mN.largeIcon != null) { 5994 mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon); 5995 } 5996 5997 // Determine the left and right icons 5998 Icon leftIcon = p.mHideLeftIcon ? null : mN.mLargeIcon; 5999 Icon rightIcon = p.mHideRightIcon ? null 6000 : (p.mPromotedPicture != null ? p.mPromotedPicture : mN.mLargeIcon); 6001 6002 // Apply the left icon (without duplicating the bitmap) 6003 if (leftIcon != rightIcon || leftIcon == null) { 6004 // If the leftIcon is explicitly hidden or different from the rightIcon, then set it 6005 // explicitly and make sure it won't take the right_icon drawable. 6006 contentView.setImageViewIcon(R.id.left_icon, leftIcon); 6007 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 0); 6008 } else { 6009 // If the leftIcon equals the rightIcon, just set the flag to use the right_icon 6010 // drawable. This avoids the view having two copies of the same bitmap. 6011 contentView.setIntTag(R.id.left_icon, R.id.tag_uses_right_icon_drawable, 1); 6012 } 6013 6014 // Always calculate dimens to populate `result` for the GONE case 6015 boolean isPromotedPicture = p.mPromotedPicture != null; 6016 calculateRightIconDimens(rightIcon, isPromotedPicture, result); 6017 6018 // Bind the right icon 6019 if (rightIcon != null) { 6020 contentView.setViewLayoutWidth(R.id.right_icon, 6021 result.mRightIconWidthDp, TypedValue.COMPLEX_UNIT_DIP); 6022 contentView.setViewLayoutHeight(R.id.right_icon, 6023 result.mRightIconHeightDp, TypedValue.COMPLEX_UNIT_DIP); 6024 contentView.setViewVisibility(R.id.right_icon, View.VISIBLE); 6025 contentView.setImageViewIcon(R.id.right_icon, rightIcon); 6026 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 6027 isPromotedPicture ? 1 : 0); 6028 processLargeLegacyIcon(rightIcon, contentView, p); 6029 } else { 6030 // The "reset" doesn't clear the drawable, so we do it here. This clear is 6031 // important because the presence of a drawable in this view (regardless of the 6032 // visibility) is used by NotificationGroupingUtil to set the visibility. 6033 contentView.setImageViewIcon(R.id.right_icon, null); 6034 contentView.setIntTag(R.id.right_icon, R.id.tag_keep_when_showing_left_icon, 0); 6035 } 6036 } 6037 bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p)6038 private void bindNotificationHeader(RemoteViews contentView, StandardTemplateParams p) { 6039 bindSmallIcon(contentView, p); 6040 // Populate text left-to-right so that separators are only shown between strings 6041 boolean hasTextToLeft = bindHeaderAppName(contentView, p, false /* force */); 6042 hasTextToLeft |= bindHeaderTextSecondary(contentView, p, hasTextToLeft); 6043 hasTextToLeft |= bindHeaderText(contentView, p, hasTextToLeft); 6044 if (!hasTextToLeft) { 6045 // If there's still no text, force add the app name so there is some text. 6046 hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */); 6047 } 6048 bindHeaderChronometerAndTime(contentView, p, hasTextToLeft); 6049 bindPhishingAlertIcon(contentView, p); 6050 bindProfileBadge(contentView, p); 6051 bindAlertedIcon(contentView, p); 6052 bindExpandButton(contentView, p); 6053 mN.mUsesStandardHeader = true; 6054 } 6055 bindExpandButton(RemoteViews contentView, StandardTemplateParams p)6056 private void bindExpandButton(RemoteViews contentView, StandardTemplateParams p) { 6057 // set default colors 6058 int bgColor = getBackgroundColor(p); 6059 int pillColor = Colors.flattenAlpha(getColors(p).getProtectionColor(), bgColor); 6060 int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor); 6061 contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor); 6062 contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor); 6063 // Use different highlighted colors for conversations' unread count 6064 if (p.mHighlightExpander) { 6065 pillColor = Colors.flattenAlpha( 6066 getColors(p).getTertiaryFixedDimAccentColor(), bgColor); 6067 textColor = Colors.flattenAlpha( 6068 getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor); 6069 } 6070 contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor); 6071 contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor); 6072 } 6073 bindHeaderChronometerAndTime(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6074 private void bindHeaderChronometerAndTime(RemoteViews contentView, 6075 StandardTemplateParams p, boolean hasTextToLeft) { 6076 if (!p.mHideTime && showsTimeOrChronometer()) { 6077 if (hasTextToLeft) { 6078 contentView.setViewVisibility(R.id.time_divider, View.VISIBLE); 6079 setTextViewColorSecondary(contentView, R.id.time_divider, p); 6080 } 6081 if (mN.extras.getBoolean(EXTRA_SHOW_CHRONOMETER)) { 6082 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 6083 contentView.setLong(R.id.chronometer, "setBase", mN.getWhen() 6084 + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 6085 contentView.setBoolean(R.id.chronometer, "setStarted", true); 6086 boolean countsDown = mN.extras.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN); 6087 contentView.setChronometerCountDown(R.id.chronometer, countsDown); 6088 setTextViewColorSecondary(contentView, R.id.chronometer, p); 6089 } else { 6090 contentView.setViewVisibility(R.id.time, View.VISIBLE); 6091 contentView.setLong(R.id.time, "setTime", mN.getWhen()); 6092 setTextViewColorSecondary(contentView, R.id.time, p); 6093 } 6094 } else { 6095 // We still want a time to be set but gone, such that we can show and hide it 6096 // on demand in case it's a child notification without anything in the header 6097 contentView.setLong(R.id.time, "setTime", mN.getWhen() != 0 ? mN.getWhen() : 6098 mN.creationTime); 6099 setTextViewColorSecondary(contentView, R.id.time, p); 6100 } 6101 } 6102 6103 /** 6104 * @return true if the header text will be visible 6105 */ bindHeaderText(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6106 private boolean bindHeaderText(RemoteViews contentView, StandardTemplateParams p, 6107 boolean hasTextToLeft) { 6108 if (p.mHideSubText) { 6109 return false; 6110 } 6111 CharSequence headerText = p.mSubText; 6112 if (headerText == null && mStyle != null && mStyle.mSummaryTextSet 6113 && mStyle.hasSummaryInHeader()) { 6114 headerText = mStyle.mSummaryText; 6115 } 6116 if (headerText == null 6117 && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 6118 && mN.extras.getCharSequence(EXTRA_INFO_TEXT) != null) { 6119 headerText = mN.extras.getCharSequence(EXTRA_INFO_TEXT); 6120 } 6121 if (!TextUtils.isEmpty(headerText)) { 6122 contentView.setTextViewText(R.id.header_text, ensureColorSpanContrastOrStripStyling( 6123 processLegacyText(headerText), p)); 6124 setTextViewColorSecondary(contentView, R.id.header_text, p); 6125 contentView.setViewVisibility(R.id.header_text, View.VISIBLE); 6126 if (hasTextToLeft) { 6127 contentView.setViewVisibility(R.id.header_text_divider, View.VISIBLE); 6128 setTextViewColorSecondary(contentView, R.id.header_text_divider, p); 6129 } 6130 return true; 6131 } 6132 return false; 6133 } 6134 6135 /** 6136 * @return true if the secondary header text will be visible 6137 */ bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, boolean hasTextToLeft)6138 private boolean bindHeaderTextSecondary(RemoteViews contentView, StandardTemplateParams p, 6139 boolean hasTextToLeft) { 6140 if (p.mHideSubText) { 6141 return false; 6142 } 6143 if (!TextUtils.isEmpty(p.mHeaderTextSecondary)) { 6144 contentView.setTextViewText(R.id.header_text_secondary, 6145 ensureColorSpanContrastOrStripStyling( 6146 processLegacyText(p.mHeaderTextSecondary), p)); 6147 setTextViewColorSecondary(contentView, R.id.header_text_secondary, p); 6148 contentView.setViewVisibility(R.id.header_text_secondary, View.VISIBLE); 6149 if (hasTextToLeft) { 6150 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.VISIBLE); 6151 setTextViewColorSecondary(contentView, R.id.header_text_secondary_divider, p); 6152 } 6153 return true; 6154 } 6155 return false; 6156 } 6157 6158 /** 6159 * @hide 6160 */ 6161 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) loadHeaderAppName()6162 public String loadHeaderAppName() { 6163 return mN.loadHeaderAppName(mContext); 6164 } 6165 6166 /** 6167 * @return true if the app name will be visible 6168 */ bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, boolean force)6169 private boolean bindHeaderAppName(RemoteViews contentView, StandardTemplateParams p, 6170 boolean force) { 6171 if (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED && !force) { 6172 // unless the force flag is set, don't show the app name in the minimized state. 6173 return false; 6174 } 6175 if (p.mHeaderless && p.hasTitle()) { 6176 // the headerless template will have the TITLE in this position; return true to 6177 // keep the divider visible between that title and the next text element. 6178 return true; 6179 } 6180 if (p.mHideAppName) { 6181 // The app name is being hidden, so we definitely want to return here. 6182 // Assume that there is a title which will replace it in the header. 6183 return p.hasTitle(); 6184 } 6185 contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE); 6186 contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName()); 6187 contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p)); 6188 return true; 6189 } 6190 6191 /** 6192 * Determines if the notification should be colorized *for the purposes of applying colors*. 6193 * If this is the minimized view of a colorized notification, this will return false so that 6194 * internal coloring logic can still render the notification normally. 6195 */ isBackgroundColorized(StandardTemplateParams p)6196 private boolean isBackgroundColorized(StandardTemplateParams p) { 6197 return p.allowColorization && mN.isColorized(); 6198 } 6199 isCallActionColorCustomizable()6200 private boolean isCallActionColorCustomizable() { 6201 // NOTE: this doesn't need to check StandardTemplateParams.allowColorization because 6202 // that is only used for disallowing colorization of headers for the minimized state, 6203 // and neither of those conditions applies when showing actions. 6204 // Not requiring StandardTemplateParams as an argument simplifies the creation process. 6205 return mN.isColorized() && mContext.getResources().getBoolean( 6206 R.bool.config_callNotificationActionColorsRequireColorized); 6207 } 6208 bindSmallIcon(RemoteViews contentView, StandardTemplateParams p)6209 private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) { 6210 if (Flags.notificationsUseAppIcon()) { 6211 // Override small icon with app icon 6212 mN.mSmallIcon = Icon.createWithResource(mContext, 6213 mN.getHeaderAppIconRes(mContext)); 6214 } else if (mN.mSmallIcon == null && mN.icon != 0) { 6215 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon); 6216 } 6217 6218 boolean usingAppIcon = false; 6219 if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) { 6220 // Use the app icon in the view 6221 int appIconRes = mN.getHeaderAppIconRes(mContext); 6222 if (appIconRes != 0) { 6223 mN.mAppIcon = Icon.createWithResource(mContext, appIconRes); 6224 contentView.setImageViewIcon(R.id.icon, mN.mAppIcon); 6225 usingAppIcon = true; 6226 } else { 6227 Log.w(TAG, "bindSmallIcon: could not get the app icon"); 6228 } 6229 } 6230 if (!usingAppIcon) { 6231 contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon); 6232 } 6233 contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel); 6234 6235 // Don't change color if we're using the app icon. 6236 if (!Flags.notificationsUseAppIcon() && !usingAppIcon) { 6237 processSmallIconColor(mN.mSmallIcon, contentView, p); 6238 } 6239 } 6240 6241 /** 6242 * @return true if the built notification will show the time or the chronometer; false 6243 * otherwise 6244 */ showsTimeOrChronometer()6245 private boolean showsTimeOrChronometer() { 6246 return mN.showsTime() || mN.showsChronometer(); 6247 } 6248 resetStandardTemplateWithActions(RemoteViews big)6249 private void resetStandardTemplateWithActions(RemoteViews big) { 6250 // actions_container is only reset when there are no actions to avoid focus issues with 6251 // remote inputs. 6252 big.setViewVisibility(R.id.actions, View.GONE); 6253 big.removeAllViews(R.id.actions); 6254 6255 big.setViewVisibility(R.id.notification_material_reply_container, View.GONE); 6256 big.setTextViewText(R.id.notification_material_reply_text_1, null); 6257 big.setViewVisibility(R.id.notification_material_reply_text_1_container, View.GONE); 6258 big.setViewVisibility(R.id.notification_material_reply_progress, View.GONE); 6259 6260 big.setViewVisibility(R.id.notification_material_reply_text_2, View.GONE); 6261 big.setTextViewText(R.id.notification_material_reply_text_2, null); 6262 big.setViewVisibility(R.id.notification_material_reply_text_3, View.GONE); 6263 big.setTextViewText(R.id.notification_material_reply_text_3, null); 6264 6265 // This may get erased by bindSnoozeAction 6266 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 6267 RemoteViews.MARGIN_BOTTOM, R.dimen.notification_content_margin); 6268 } 6269 bindSnoozeAction(RemoteViews big, StandardTemplateParams p)6270 private void bindSnoozeAction(RemoteViews big, StandardTemplateParams p) { 6271 boolean hideSnoozeButton = mN.isFgsOrUij() 6272 || mN.fullScreenIntent != null 6273 || isBackgroundColorized(p) 6274 || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG; 6275 big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton); 6276 if (hideSnoozeButton) { 6277 // Only hide; NotificationContentView will show it when it adds the click listener 6278 big.setViewVisibility(R.id.snooze_button, View.GONE); 6279 } 6280 6281 final boolean snoozeEnabled = !hideSnoozeButton 6282 && mContext.getContentResolver() != null 6283 && isSnoozeSettingEnabled(); 6284 if (snoozeEnabled) { 6285 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 6286 RemoteViews.MARGIN_BOTTOM, 0); 6287 } 6288 } 6289 isSnoozeSettingEnabled()6290 private boolean isSnoozeSettingEnabled() { 6291 try { 6292 return Settings.Secure.getIntForUser(mContext.getContentResolver(), 6293 Settings.Secure.SHOW_NOTIFICATION_SNOOZE, 0, UserHandle.USER_CURRENT) == 1; 6294 } catch (SecurityException ex) { 6295 // Most 3p apps can't access this snooze setting, so their NotificationListeners 6296 // would be unable to create notification views if we propagated this exception. 6297 return false; 6298 } 6299 } 6300 6301 /** 6302 * Returns the actions that are not contextual. 6303 */ getNonContextualActions()6304 private @NonNull List<Notification.Action> getNonContextualActions() { 6305 if (mActions == null) return Collections.emptyList(); 6306 List<Notification.Action> standardActions = new ArrayList<>(); 6307 for (Notification.Action action : mActions) { 6308 if (!action.isContextual()) { 6309 standardActions.add(action); 6310 } 6311 } 6312 return standardActions; 6313 } 6314 applyStandardTemplateWithActions(int layoutId, StandardTemplateParams p, TemplateBindResult result)6315 private RemoteViews applyStandardTemplateWithActions(int layoutId, 6316 StandardTemplateParams p, TemplateBindResult result) { 6317 RemoteViews big = applyStandardTemplate(layoutId, p, result); 6318 6319 resetStandardTemplateWithActions(big); 6320 bindSnoozeAction(big, p); 6321 // color the snooze and bubble actions with the theme color 6322 ColorStateList actionColor = ColorStateList.valueOf(getStandardActionColor(p)); 6323 big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor); 6324 big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor); 6325 6326 boolean validRemoteInput = false; 6327 6328 // In the UI, contextual actions appear separately from the standard actions, so we 6329 // filter them out here. 6330 List<Notification.Action> nonContextualActions = getNonContextualActions(); 6331 6332 int numActions = Math.min(nonContextualActions.size(), MAX_ACTION_BUTTONS); 6333 boolean emphasizedMode = mN.fullScreenIntent != null 6334 || p.mCallStyleActions 6335 || ((mN.flags & FLAG_FSI_REQUESTED_BUT_DENIED) != 0); 6336 6337 if (p.mCallStyleActions) { 6338 // Clear view padding to allow buttons to start on the left edge. 6339 // This must be done before 'setEmphasizedMode' which sets top/bottom margins. 6340 big.setViewPadding(R.id.actions, 0, 0, 0, 0); 6341 // Add an optional indent that will make buttons start at the correct column when 6342 // there is enough space to do so (and fall back to the left edge if not). 6343 big.setInt(R.id.actions, "setCollapsibleIndentDimen", 6344 R.dimen.call_notification_collapsible_indent); 6345 if (evenlyDividedCallStyleActionLayout()) { 6346 if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { 6347 Log.d(TAG, "setting evenly divided mode on action list"); 6348 } 6349 big.setBoolean(R.id.actions, "setEvenlyDividedMode", true); 6350 } 6351 } 6352 big.setBoolean(R.id.actions, "setEmphasizedMode", emphasizedMode); 6353 if (numActions > 0 && !p.mHideActions) { 6354 big.setViewVisibility(R.id.actions_container, View.VISIBLE); 6355 big.setViewVisibility(R.id.actions, View.VISIBLE); 6356 big.setViewLayoutMarginDimen(R.id.notification_action_list_margin_target, 6357 RemoteViews.MARGIN_BOTTOM, 0); 6358 for (int i = 0; i < numActions; i++) { 6359 Action action = nonContextualActions.get(i); 6360 6361 boolean actionHasValidInput = hasValidRemoteInput(action); 6362 validRemoteInput |= actionHasValidInput; 6363 6364 final RemoteViews button = generateActionButton(action, emphasizedMode, p); 6365 if (actionHasValidInput && !emphasizedMode) { 6366 // Clear the drawable 6367 button.setInt(R.id.action0, "setBackgroundResource", 0); 6368 } 6369 if (emphasizedMode && i > 0) { 6370 // Clear start margin from non-first buttons to reduce the gap between them. 6371 // (8dp remaining gap is from all buttons' standard 4dp inset). 6372 button.setViewLayoutMarginDimen(R.id.action0, RemoteViews.MARGIN_START, 0); 6373 } 6374 big.addView(R.id.actions, button); 6375 } 6376 } else { 6377 big.setViewVisibility(R.id.actions_container, View.GONE); 6378 } 6379 6380 RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle( 6381 mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class); 6382 if (validRemoteInput && replyText != null && replyText.length > 0 6383 && !TextUtils.isEmpty(replyText[0].getText()) 6384 && p.maxRemoteInputHistory > 0) { 6385 boolean showSpinner = mN.extras.getBoolean(EXTRA_SHOW_REMOTE_INPUT_SPINNER); 6386 big.setViewVisibility(R.id.notification_material_reply_container, View.VISIBLE); 6387 big.setViewVisibility(R.id.notification_material_reply_text_1_container, 6388 View.VISIBLE); 6389 big.setTextViewText(R.id.notification_material_reply_text_1, 6390 ensureColorSpanContrastOrStripStyling(replyText[0].getText(), p)); 6391 setTextViewColorSecondary(big, R.id.notification_material_reply_text_1, p); 6392 big.setViewVisibility(R.id.notification_material_reply_progress, 6393 showSpinner ? View.VISIBLE : View.GONE); 6394 big.setProgressIndeterminateTintList( 6395 R.id.notification_material_reply_progress, 6396 ColorStateList.valueOf(getPrimaryAccentColor(p))); 6397 6398 if (replyText.length > 1 && !TextUtils.isEmpty(replyText[1].getText()) 6399 && p.maxRemoteInputHistory > 1) { 6400 big.setViewVisibility(R.id.notification_material_reply_text_2, View.VISIBLE); 6401 big.setTextViewText(R.id.notification_material_reply_text_2, 6402 ensureColorSpanContrastOrStripStyling(replyText[1].getText(), p)); 6403 setTextViewColorSecondary(big, R.id.notification_material_reply_text_2, p); 6404 6405 if (replyText.length > 2 && !TextUtils.isEmpty(replyText[2].getText()) 6406 && p.maxRemoteInputHistory > 2) { 6407 big.setViewVisibility( 6408 R.id.notification_material_reply_text_3, View.VISIBLE); 6409 big.setTextViewText(R.id.notification_material_reply_text_3, 6410 ensureColorSpanContrastOrStripStyling(replyText[2].getText(), p)); 6411 setTextViewColorSecondary(big, R.id.notification_material_reply_text_3, p); 6412 } 6413 } 6414 } 6415 6416 return big; 6417 } 6418 hasValidRemoteInput(Action action)6419 private boolean hasValidRemoteInput(Action action) { 6420 if (TextUtils.isEmpty(action.title) || action.actionIntent == null) { 6421 // Weird actions 6422 return false; 6423 } 6424 6425 RemoteInput[] remoteInputs = action.getRemoteInputs(); 6426 if (remoteInputs == null) { 6427 return false; 6428 } 6429 6430 for (RemoteInput r : remoteInputs) { 6431 CharSequence[] choices = r.getChoices(); 6432 if (r.getAllowFreeFormInput() || (choices != null && choices.length != 0)) { 6433 return true; 6434 } 6435 } 6436 return false; 6437 } 6438 6439 /** 6440 * Construct a RemoteViews representing the standard notification layout. 6441 * 6442 * @deprecated For performance and system health reasons, this API is no longer required to 6443 * be used directly by the System UI when rendering Notifications to the user. While the UI 6444 * returned by this method will still represent the content of the Notification being 6445 * built, it may differ from the visual style of the system. 6446 * 6447 * NOTE: this API has always had severe limitations; for example it does not support any 6448 * interactivity, it ignores the app theme, it hard-codes the colors from the system theme 6449 * at the time it is called, and it does Bitmap decoding on the main thread which can cause 6450 * UI jank. 6451 */ 6452 @Deprecated createContentView()6453 public RemoteViews createContentView() { 6454 return createContentView(false /* increasedheight */ ); 6455 } 6456 6457 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 6458 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 6459 // the change's state in NotificationManagerService were very complex. While it's possible 6460 // apps can detect the change, it's most likely that the changes will simply result in 6461 // visual regressions. 6462 @SuppressWarnings("AndroidFrameworkCompatChange") fullyCustomViewRequiresDecoration(boolean fromStyle)6463 private boolean fullyCustomViewRequiresDecoration(boolean fromStyle) { 6464 // Custom views which come from a platform style class are safe, and thus do not need to 6465 // be wrapped. Any subclass of those styles has the opportunity to make arbitrary 6466 // changes to the RemoteViews, and thus can't be trusted as a fully vetted view. 6467 if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { 6468 return false; 6469 } 6470 return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S; 6471 } 6472 minimallyDecoratedContentView(@onNull RemoteViews customContent)6473 private RemoteViews minimallyDecoratedContentView(@NonNull RemoteViews customContent) { 6474 StandardTemplateParams p = mParams.reset() 6475 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 6476 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 6477 .fillTextsFrom(this); 6478 TemplateBindResult result = new TemplateBindResult(); 6479 RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result); 6480 buildCustomContentIntoTemplate(mContext, standard, customContent, 6481 p, result); 6482 return standard; 6483 } 6484 minimallyDecoratedBigContentView(@onNull RemoteViews customContent)6485 private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) { 6486 StandardTemplateParams p = mParams.reset() 6487 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 6488 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 6489 .fillTextsFrom(this); 6490 TemplateBindResult result = new TemplateBindResult(); 6491 RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(), 6492 p, result); 6493 buildCustomContentIntoTemplate(mContext, standard, customContent, 6494 p, result); 6495 makeHeaderExpanded(standard); 6496 return standard; 6497 } 6498 minimallyDecoratedHeadsUpContentView( @onNull RemoteViews customContent)6499 private RemoteViews minimallyDecoratedHeadsUpContentView( 6500 @NonNull RemoteViews customContent) { 6501 StandardTemplateParams p = mParams.reset() 6502 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 6503 .decorationType(StandardTemplateParams.DECORATION_MINIMAL) 6504 .fillTextsFrom(this); 6505 TemplateBindResult result = new TemplateBindResult(); 6506 RemoteViews standard = applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), 6507 p, result); 6508 buildCustomContentIntoTemplate(mContext, standard, customContent, 6509 p, result); 6510 return standard; 6511 } 6512 6513 /** 6514 * Construct a RemoteViews for the smaller content view. 6515 * 6516 * @param increasedHeight true if this layout be created with an increased height. Some 6517 * styles may support showing more then just that basic 1U size 6518 * and the system may decide to render important notifications 6519 * slightly bigger even when collapsed. 6520 * 6521 * @hide 6522 */ createContentView(boolean increasedHeight)6523 public RemoteViews createContentView(boolean increasedHeight) { 6524 if (useExistingRemoteView(mN.contentView)) { 6525 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 6526 ? minimallyDecoratedContentView(mN.contentView) : mN.contentView; 6527 } else if (mStyle != null) { 6528 final RemoteViews styleView = mStyle.makeContentView(increasedHeight); 6529 if (styleView != null) { 6530 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 6531 ? minimallyDecoratedContentView(styleView) : styleView; 6532 } 6533 } 6534 StandardTemplateParams p = mParams.reset() 6535 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 6536 .fillTextsFrom(this); 6537 return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */); 6538 } 6539 useExistingRemoteView(RemoteViews customContent)6540 private boolean useExistingRemoteView(RemoteViews customContent) { 6541 if (customContent == null) { 6542 return false; 6543 } 6544 if (styleDisplaysCustomViewInline()) { 6545 // the provided custom view is intended to be wrapped by the style. 6546 return false; 6547 } 6548 if (fullyCustomViewRequiresDecoration(false) 6549 && STANDARD_LAYOUTS.contains(customContent.getLayoutId())) { 6550 // If the app's custom views are objects returned from Builder.create*ContentView() 6551 // then the app is most likely attempting to spoof the user. Even if they are not, 6552 // the result would be broken (b/189189308) so we will ignore it. 6553 Log.w(TAG, "For apps targeting S, a custom content view that is a modified " 6554 + "version of any standard layout is disallowed."); 6555 return false; 6556 } 6557 return true; 6558 } 6559 6560 /** 6561 * Construct a RemoteViews representing the expanded notification layout. 6562 * 6563 * @deprecated For performance and system health reasons, this API is no longer required to 6564 * be used directly by the System UI when rendering Notifications to the user. While the UI 6565 * returned by this method will still represent the content of the Notification being 6566 * built, it may differ from the visual style of the system. 6567 * 6568 * NOTE: this API has always had severe limitations; for example it does not support any 6569 * interactivity, it ignores the app theme, it hard-codes the colors from the system theme 6570 * at the time it is called, and it does Bitmap decoding on the main thread which can cause 6571 * UI jank. 6572 */ 6573 @Deprecated createBigContentView()6574 public RemoteViews createBigContentView() { 6575 RemoteViews result = null; 6576 if (useExistingRemoteView(mN.bigContentView)) { 6577 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 6578 ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView; 6579 } 6580 if (mStyle != null) { 6581 result = mStyle.makeBigContentView(); 6582 if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) { 6583 result = minimallyDecoratedBigContentView(result); 6584 } 6585 } 6586 if (result == null) { 6587 if (bigContentViewRequired()) { 6588 StandardTemplateParams p = mParams.reset() 6589 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 6590 .allowTextWithProgress(true) 6591 .fillTextsFrom(this); 6592 result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p, 6593 null /* result */); 6594 } 6595 } 6596 makeHeaderExpanded(result); 6597 return result; 6598 } 6599 6600 // This code is executed on behalf of other apps' notifications, sometimes even by 3p apps, 6601 // a use case that is not supported by the Compat Framework library. Workarounds to resolve 6602 // the change's state in NotificationManagerService were very complex. While it's possible 6603 // apps can detect the change, it's most likely that the changes will simply result in 6604 // visual regressions. 6605 @SuppressWarnings("AndroidFrameworkCompatChange") bigContentViewRequired()6606 private boolean bigContentViewRequired() { 6607 if (Flags.notificationExpansionOptional()) { 6608 // Notifications without a bigContentView, style, or actions do not need to expand 6609 boolean exempt = mN.bigContentView == null 6610 && mStyle == null && mActions.size() == 0; 6611 return !exempt; 6612 } 6613 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { 6614 return true; 6615 } 6616 // Notifications with contentView and without a bigContentView, style, or actions would 6617 // not have an expanded state before S, so showing the standard template expanded state 6618 // usually looks wrong, so we keep it simple and don't show the expanded state. 6619 boolean exempt = mN.contentView != null && mN.bigContentView == null 6620 && mStyle == null && mActions.size() == 0; 6621 return !exempt; 6622 } 6623 6624 /** 6625 * Construct a RemoteViews for the final notification header only. This will not be 6626 * colorized. 6627 * 6628 * @hide 6629 */ makeNotificationGroupHeader()6630 public RemoteViews makeNotificationGroupHeader() { 6631 return makeNotificationHeader(mParams.reset() 6632 .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER) 6633 .fillTextsFrom(this)); 6634 } 6635 6636 /** 6637 * Construct a RemoteViews for the final notification header only. This will not be 6638 * colorized. 6639 * 6640 * @param p the template params to inflate this with 6641 */ makeNotificationHeader(StandardTemplateParams p)6642 private RemoteViews makeNotificationHeader(StandardTemplateParams p) { 6643 // Headers on their own are never colorized 6644 p.disallowColorization(); 6645 RemoteViews header = new BuilderRemoteViews(mContext.getApplicationInfo(), 6646 R.layout.notification_template_header); 6647 resetNotificationHeader(header); 6648 bindNotificationHeader(header, p); 6649 return header; 6650 } 6651 6652 /** 6653 * Construct a RemoteViews for the ambient version of the notification. 6654 * 6655 * @hide 6656 */ makeAmbientNotification()6657 public RemoteViews makeAmbientNotification() { 6658 RemoteViews headsUpContentView = createHeadsUpContentView(false /* increasedHeight */); 6659 if (headsUpContentView != null) { 6660 return headsUpContentView; 6661 } 6662 return createContentView(); 6663 } 6664 6665 /** 6666 * Adapt the Notification header if this view is used as an expanded view. 6667 * 6668 * @hide 6669 */ makeHeaderExpanded(RemoteViews result)6670 public static void makeHeaderExpanded(RemoteViews result) { 6671 if (result != null) { 6672 result.setBoolean(R.id.expand_button, "setExpanded", true); 6673 } 6674 } 6675 6676 /** 6677 * Construct a RemoteViews for the final heads-up notification layout. 6678 * 6679 * @param increasedHeight true if this layout be created with an increased height. Some 6680 * styles may support showing more then just that basic 1U size 6681 * and the system may decide to render important notifications 6682 * slightly bigger even when collapsed. 6683 * 6684 * @hide 6685 */ createHeadsUpContentView(boolean increasedHeight)6686 public RemoteViews createHeadsUpContentView(boolean increasedHeight) { 6687 if (useExistingRemoteView(mN.headsUpContentView)) { 6688 return fullyCustomViewRequiresDecoration(false /* fromStyle */) 6689 ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) 6690 : mN.headsUpContentView; 6691 } else if (mStyle != null) { 6692 final RemoteViews styleView = mStyle.makeHeadsUpContentView(increasedHeight); 6693 if (styleView != null) { 6694 return fullyCustomViewRequiresDecoration(true /* fromStyle */) 6695 ? minimallyDecoratedHeadsUpContentView(styleView) : styleView; 6696 } 6697 } else if (mActions.size() == 0) { 6698 return null; 6699 } 6700 6701 // We only want at most a single remote input history to be shown here, otherwise 6702 // the content would become squished. 6703 StandardTemplateParams p = mParams.reset() 6704 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 6705 .fillTextsFrom(this) 6706 .setMaxRemoteInputHistory(1); 6707 return applyStandardTemplateWithActions(getHeadsUpBaseLayoutResource(), p, 6708 null /* result */); 6709 } 6710 6711 /** 6712 * Construct a RemoteViews for the final compact heads-up notification layout. 6713 * @hide 6714 */ createCompactHeadsUpContentView()6715 public RemoteViews createCompactHeadsUpContentView() { 6716 // Don't show compact heads up for FSI notifications. 6717 if (mN.fullScreenIntent != null) { 6718 return createHeadsUpContentView(/* increasedHeight= */ false); 6719 } 6720 6721 if (mStyle != null) { 6722 final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView(); 6723 if (styleView != null) { 6724 return styleView; 6725 } 6726 } 6727 6728 final StandardTemplateParams p = mParams.reset() 6729 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 6730 .fillTextsFrom(this); 6731 // Notification text is shown as secondary header text 6732 // for the minimal hun when it is provided. 6733 // Time(when and chronometer) is not shown for the minimal hun. 6734 p.headerTextSecondary(p.mText).text(null).hideTime(true).summaryText(""); 6735 6736 return applyStandardTemplate( 6737 getCompactHeadsUpBaseLayoutResource(), p, 6738 null /* result */); 6739 } 6740 6741 /** 6742 * Construct a RemoteViews representing the heads up notification layout. 6743 * 6744 * @deprecated For performance and system health reasons, this API is no longer required to 6745 * be used directly by the System UI when rendering Notifications to the user. While the UI 6746 * returned by this method will still represent the content of the Notification being 6747 * built, it may differ from the visual style of the system. 6748 * 6749 * NOTE: this API has always had severe limitations; for example it does not support any 6750 * interactivity, it ignores the app theme, it hard-codes the colors from the system theme 6751 * at the time it is called, and it does Bitmap decoding on the main thread which can cause 6752 * UI jank. 6753 */ 6754 @Deprecated createHeadsUpContentView()6755 public RemoteViews createHeadsUpContentView() { 6756 return createHeadsUpContentView(false /* useIncreasedHeight */); 6757 } 6758 6759 /** 6760 * Construct a RemoteViews for the display in public contexts like on the lockscreen. 6761 * 6762 * @param isLowPriority is this notification low priority 6763 * @hide 6764 */ 6765 @UnsupportedAppUsage makePublicContentView(boolean isLowPriority)6766 public RemoteViews makePublicContentView(boolean isLowPriority) { 6767 if (mN.publicVersion != null) { 6768 final Builder builder = recoverBuilder(mContext, mN.publicVersion); 6769 return builder.createContentView(); 6770 } 6771 Bundle savedBundle = mN.extras; 6772 Style style = mStyle; 6773 mStyle = null; 6774 Icon largeIcon = mN.mLargeIcon; 6775 mN.mLargeIcon = null; 6776 Bitmap largeIconLegacy = mN.largeIcon; 6777 mN.largeIcon = null; 6778 ArrayList<Action> actions = mActions; 6779 mActions = new ArrayList<>(); 6780 Bundle publicExtras = new Bundle(); 6781 publicExtras.putBoolean(EXTRA_SHOW_WHEN, 6782 savedBundle.getBoolean(EXTRA_SHOW_WHEN)); 6783 publicExtras.putBoolean(EXTRA_SHOW_CHRONOMETER, 6784 savedBundle.getBoolean(EXTRA_SHOW_CHRONOMETER)); 6785 publicExtras.putBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, 6786 savedBundle.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN)); 6787 String appName = savedBundle.getString(EXTRA_SUBSTITUTE_APP_NAME); 6788 if (appName != null) { 6789 publicExtras.putString(EXTRA_SUBSTITUTE_APP_NAME, appName); 6790 } 6791 mN.extras = publicExtras; 6792 RemoteViews view; 6793 StandardTemplateParams params = mParams.reset() 6794 .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC) 6795 .fillTextsFrom(this); 6796 if (isLowPriority) { 6797 params.highlightExpander(false); 6798 } 6799 view = makeNotificationHeader(params); 6800 view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true); 6801 mN.extras = savedBundle; 6802 mN.mLargeIcon = largeIcon; 6803 mN.largeIcon = largeIconLegacy; 6804 mActions = actions; 6805 mStyle = style; 6806 return view; 6807 } 6808 6809 /** 6810 * Construct a content view for the display when low - priority 6811 * 6812 * @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise 6813 * a new subtext is created consisting of the content of the 6814 * notification. 6815 * @hide 6816 */ makeLowPriorityContentView(boolean useRegularSubtext)6817 public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) { 6818 StandardTemplateParams p = mParams.reset() 6819 .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED) 6820 .highlightExpander(false) 6821 .fillTextsFrom(this); 6822 if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) { 6823 p.summaryText(createSummaryText()); 6824 } 6825 RemoteViews header = makeNotificationHeader(p); 6826 header.setBoolean(R.id.notification_header, "setAcceptAllTouches", true); 6827 // The low priority header has no app name and shows the text 6828 header.setBoolean(R.id.notification_header, "styleTextAsTitle", true); 6829 return header; 6830 } 6831 createSummaryText()6832 private CharSequence createSummaryText() { 6833 CharSequence titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE); 6834 if (USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY) { 6835 return titleText; 6836 } 6837 SpannableStringBuilder summary = new SpannableStringBuilder(); 6838 if (titleText == null) { 6839 titleText = mN.extras.getCharSequence(Notification.EXTRA_TITLE_BIG); 6840 } 6841 BidiFormatter bidi = BidiFormatter.getInstance(); 6842 if (titleText != null) { 6843 summary.append(bidi.unicodeWrap(titleText)); 6844 } 6845 CharSequence contentText = mN.extras.getCharSequence(Notification.EXTRA_TEXT); 6846 if (titleText != null && contentText != null) { 6847 summary.append(bidi.unicodeWrap(mContext.getText( 6848 R.string.notification_header_divider_symbol_with_spaces))); 6849 } 6850 if (contentText != null) { 6851 summary.append(bidi.unicodeWrap(contentText)); 6852 } 6853 return summary; 6854 } 6855 generateActionButton(Action action, boolean emphasizedMode, StandardTemplateParams p)6856 private RemoteViews generateActionButton(Action action, boolean emphasizedMode, 6857 StandardTemplateParams p) { 6858 final boolean tombstone = (action.actionIntent == null); 6859 final RemoteViews button = new BuilderRemoteViews(mContext.getApplicationInfo(), 6860 getActionButtonLayoutResource(emphasizedMode, tombstone)); 6861 if (!tombstone) { 6862 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 6863 } 6864 button.setContentDescription(R.id.action0, action.title); 6865 if (action.mRemoteInputs != null) { 6866 button.setRemoteInputs(R.id.action0, action.mRemoteInputs); 6867 } 6868 if (emphasizedMode) { 6869 // change the background bgColor 6870 CharSequence title = action.title; 6871 int buttonFillColor = getColors(p).getSecondaryAccentColor(); 6872 if (tombstone) { 6873 buttonFillColor = setAlphaComponentByFloatDimen(mContext, 6874 ContrastColorUtil.resolveSecondaryColor( 6875 mContext, getColors(p).getBackgroundColor(), mInNightMode), 6876 R.dimen.notification_action_disabled_container_alpha); 6877 } 6878 if (Flags.cleanUpSpansAndNewLines()) { 6879 if (!isLegacy()) { 6880 // Check for a full-length span color to use as the button fill color. 6881 Integer fullLengthColor = getFullLengthSpanColor(title); 6882 if (fullLengthColor != null) { 6883 // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. 6884 int notifBackgroundColor = getColors(p).getBackgroundColor(); 6885 buttonFillColor = ensureButtonFillContrast( 6886 fullLengthColor, notifBackgroundColor); 6887 } 6888 } 6889 } else { 6890 if (isLegacy()) { 6891 title = ContrastColorUtil.clearColorSpans(title); 6892 } else { 6893 // Check for a full-length span color to use as the button fill color. 6894 Integer fullLengthColor = getFullLengthSpanColor(title); 6895 if (fullLengthColor != null) { 6896 // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. 6897 int notifBackgroundColor = getColors(p).getBackgroundColor(); 6898 buttonFillColor = ensureButtonFillContrast( 6899 fullLengthColor, notifBackgroundColor); 6900 } 6901 // Remove full-length color spans 6902 // and ensure text contrast with the button fill. 6903 title = ContrastColorUtil.ensureColorSpanContrast(title, buttonFillColor); 6904 } 6905 } 6906 6907 6908 final CharSequence label = ensureColorSpanContrastOrStripStyling(title, p); 6909 if (p.mCallStyleActions && evenlyDividedCallStyleActionLayout()) { 6910 if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { 6911 Log.d(TAG, "new action layout enabled, gluing instead of setting text"); 6912 } 6913 button.setCharSequence(R.id.action0, "glueLabel", label); 6914 } else { 6915 button.setTextViewText(R.id.action0, label); 6916 } 6917 int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, 6918 buttonFillColor, mInNightMode); 6919 if (tombstone) { 6920 textColor = setAlphaComponentByFloatDimen(mContext, 6921 ContrastColorUtil.resolveSecondaryColor( 6922 mContext, getColors(p).getBackgroundColor(), mInNightMode), 6923 R.dimen.notification_action_disabled_content_alpha); 6924 } 6925 button.setTextColor(R.id.action0, textColor); 6926 // We only want about 20% alpha for the ripple 6927 final int rippleColor = (textColor & 0x00ffffff) | 0x33000000; 6928 button.setColorStateList(R.id.action0, "setRippleColor", 6929 ColorStateList.valueOf(rippleColor)); 6930 button.setColorStateList(R.id.action0, "setButtonBackground", 6931 ColorStateList.valueOf(buttonFillColor)); 6932 if (p.mCallStyleActions) { 6933 if (evenlyDividedCallStyleActionLayout()) { 6934 if (CallStyle.DEBUG_NEW_ACTION_LAYOUT) { 6935 Log.d(TAG, "new action layout enabled, gluing instead of setting icon"); 6936 } 6937 button.setIcon(R.id.action0, "glueIcon", action.getIcon()); 6938 } else { 6939 button.setImageViewIcon(R.id.action0, action.getIcon()); 6940 } 6941 boolean priority = action.getExtras().getBoolean(CallStyle.KEY_ACTION_PRIORITY); 6942 button.setBoolean(R.id.action0, "setIsPriority", priority); 6943 int minWidthDimen = 6944 priority ? R.dimen.call_notification_system_action_min_width : 0; 6945 button.setIntDimen(R.id.action0, "setMinimumWidth", minWidthDimen); 6946 } 6947 } else { 6948 button.setTextViewText(R.id.action0, ensureColorSpanContrastOrStripStyling( 6949 action.title, p)); 6950 button.setTextColor(R.id.action0, getStandardActionColor(p)); 6951 } 6952 // CallStyle notifications add action buttons which don't actually exist in mActions, 6953 // so we have to omit the index in that case. 6954 int actionIndex = mActions.indexOf(action); 6955 if (actionIndex != -1) { 6956 button.setIntTag(R.id.action0, R.id.notification_action_index_tag, actionIndex); 6957 } 6958 return button; 6959 } 6960 getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone)6961 private int getActionButtonLayoutResource(boolean emphasizedMode, boolean tombstone) { 6962 if (emphasizedMode) { 6963 return tombstone ? getEmphasizedTombstoneActionLayoutResource() 6964 : getEmphasizedActionLayoutResource(); 6965 } else { 6966 return tombstone ? getActionTombstoneLayoutResource() 6967 : getActionLayoutResource(); 6968 } 6969 } 6970 6971 /** 6972 * Set the alpha component of {@code color} to be {@code alphaDimenResId}. 6973 */ setAlphaComponentByFloatDimen(Context context, @ColorInt int color, @DimenRes int alphaDimenResId)6974 private static int setAlphaComponentByFloatDimen(Context context, @ColorInt int color, 6975 @DimenRes int alphaDimenResId) { 6976 final TypedValue alphaValue = new TypedValue(); 6977 context.getResources().getValue(alphaDimenResId, alphaValue, true); 6978 return ColorUtils.setAlphaComponent(color, Math.round(alphaValue.getFloat() * 255)); 6979 } 6980 6981 /** 6982 * Extract the color from a full-length span from the text. 6983 * 6984 * @param charSequence the charSequence containing spans 6985 * @return the raw color of the text's last full-length span containing a color, or null if 6986 * no full-length span sets the text color. 6987 * @hide 6988 */ 6989 @VisibleForTesting 6990 @Nullable getFullLengthSpanColor(CharSequence charSequence)6991 public static Integer getFullLengthSpanColor(CharSequence charSequence) { 6992 // NOTE: this method preserves the functionality that for a CharSequence with multiple 6993 // full-length spans, the color of the last one is used. 6994 Integer result = null; 6995 if (charSequence instanceof Spanned) { 6996 Spanned ss = (Spanned) charSequence; 6997 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 6998 // First read through all full-length spans to get the button fill color, which will 6999 // be used as the background color for ensuring contrast of non-full-length spans. 7000 for (Object span : spans) { 7001 int spanStart = ss.getSpanStart(span); 7002 int spanEnd = ss.getSpanEnd(span); 7003 boolean fullLength = (spanEnd - spanStart) == charSequence.length(); 7004 if (!fullLength) { 7005 continue; 7006 } 7007 if (span instanceof TextAppearanceSpan) { 7008 TextAppearanceSpan originalSpan = (TextAppearanceSpan) span; 7009 ColorStateList textColor = originalSpan.getTextColor(); 7010 if (textColor != null) { 7011 result = textColor.getDefaultColor(); 7012 } 7013 } else if (span instanceof ForegroundColorSpan) { 7014 ForegroundColorSpan originalSpan = (ForegroundColorSpan) span; 7015 result = originalSpan.getForegroundColor(); 7016 } 7017 } 7018 } 7019 return result; 7020 } 7021 7022 /** 7023 * @hide 7024 */ ensureColorSpanContrastOrStripStyling(CharSequence cs, StandardTemplateParams p)7025 public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs, 7026 StandardTemplateParams p) { 7027 return ensureColorSpanContrastOrStripStyling(cs, getBackgroundColor(p)); 7028 } 7029 7030 /** 7031 * @hide 7032 */ ensureColorSpanContrastOrStripStyling(CharSequence cs, int buttonFillColor)7033 public CharSequence ensureColorSpanContrastOrStripStyling(CharSequence cs, 7034 int buttonFillColor) { 7035 if (Flags.cleanUpSpansAndNewLines()) { 7036 return stripStyling(cs); 7037 } 7038 7039 return ContrastColorUtil.ensureColorSpanContrast(cs, buttonFillColor); 7040 } 7041 7042 /** 7043 * Ensures contrast on color spans against a background color. 7044 * Note that any full-length color spans will be removed instead of being contrasted. 7045 * 7046 * @hide 7047 */ 7048 @VisibleForTesting ensureColorSpanContrast(CharSequence charSequence, StandardTemplateParams p)7049 public CharSequence ensureColorSpanContrast(CharSequence charSequence, 7050 StandardTemplateParams p) { 7051 return ContrastColorUtil.ensureColorSpanContrast(charSequence, getBackgroundColor(p)); 7052 } 7053 7054 /** 7055 * Determines if the color is light or dark. Specifically, this is using the same metric as 7056 * {@link ContrastColorUtil#resolvePrimaryColor(Context, int, boolean)} and peers so that 7057 * the direction of color shift is consistent. 7058 * 7059 * @param color the color to check 7060 * @return true if the color has higher contrast with white than black 7061 * @hide 7062 */ isColorDark(int color)7063 public static boolean isColorDark(int color) { 7064 // as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint. 7065 return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474; 7066 } 7067 7068 /** 7069 * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue 7070 * as the original color, but is lightened or darkened depending on whether the background 7071 * is dark or light. 7072 * 7073 * @hide 7074 */ 7075 @VisibleForTesting ensureButtonFillContrast(int color, int bg)7076 public static int ensureButtonFillContrast(int color, int bg) { 7077 return isColorDark(bg) 7078 ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3) 7079 : ContrastColorUtil.findContrastColor(color, bg, true, 1.3); 7080 } 7081 7082 7083 /** 7084 * @return Whether we are currently building a notification from a legacy (an app that 7085 * doesn't create material notifications by itself) app. 7086 */ isLegacy()7087 private boolean isLegacy() { 7088 if (!mIsLegacyInitialized) { 7089 mIsLegacy = mContext.getApplicationInfo().targetSdkVersion 7090 < Build.VERSION_CODES.LOLLIPOP; 7091 mIsLegacyInitialized = true; 7092 } 7093 return mIsLegacy; 7094 } 7095 7096 private CharSequence processLegacyText(CharSequence charSequence) { 7097 boolean isAlreadyLightText = isLegacy() || textColorsNeedInversion(); 7098 if (isAlreadyLightText) { 7099 return getColorUtil().invertCharSequenceColors(charSequence); 7100 } else { 7101 return charSequence; 7102 } 7103 } 7104 7105 /** 7106 * Apply any necessary colors to the small icon 7107 */ 7108 private void processSmallIconColor(Icon smallIcon, RemoteViews contentView, 7109 StandardTemplateParams p) { 7110 boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, 7111 smallIcon); 7112 int color = getSmallIconColor(p); 7113 contentView.setInt(R.id.icon, "setBackgroundColor", 7114 getBackgroundColor(p)); 7115 contentView.setInt(R.id.icon, "setOriginalIconColor", 7116 colorable ? color : COLOR_INVALID); 7117 } 7118 7119 /** 7120 * Make the largeIcon dark if it's a fake smallIcon (that is, 7121 * if it's grayscale). 7122 */ 7123 // TODO: also check bounds, transparency, that sort of thing. 7124 private void processLargeLegacyIcon(Icon largeIcon, RemoteViews contentView, 7125 StandardTemplateParams p) { 7126 if (largeIcon != null && isLegacy() 7127 && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) { 7128 // resolve color will fall back to the default when legacy 7129 int color = getSmallIconColor(p); 7130 contentView.setInt(R.id.icon, "setOriginalIconColor", color); 7131 } 7132 } 7133 7134 private void sanitizeColor() { 7135 if (mN.color != COLOR_DEFAULT) { 7136 mN.color |= 0xFF000000; // no alpha for custom colors 7137 } 7138 } 7139 7140 /** 7141 * Gets the standard action button color 7142 */ 7143 private @ColorInt int getStandardActionColor(Notification.StandardTemplateParams p) { 7144 return mTintActionButtons || isBackgroundColorized(p) 7145 ? getPrimaryAccentColor(p) : getSecondaryTextColor(p); 7146 } 7147 7148 /** 7149 * Gets the foreground color of the small icon. If the notification is colorized, this 7150 * is the primary text color, otherwise it's the contrast-adjusted app-provided color. 7151 */ 7152 private @ColorInt int getSmallIconColor(StandardTemplateParams p) { 7153 return getColors(p).getContrastColor(); 7154 } 7155 7156 /** 7157 * Gets the foreground color of the small icon. If the notification is colorized, this 7158 * is the primary text color, otherwise it's the contrast-adjusted app-provided color. 7159 * @hide 7160 */ 7161 public @ColorInt int getSmallIconColor(boolean isHeader) { 7162 return getColors(/* isHeader = */ isHeader).getContrastColor(); 7163 } 7164 7165 /** 7166 * Gets the background color of the notification. 7167 * @hide 7168 */ 7169 public @ColorInt int getBackgroundColor(boolean isHeader) { 7170 return getColors(/* isHeader = */ isHeader).getBackgroundColor(); 7171 } 7172 7173 /** @return the theme's accent color for colored UI elements. */ 7174 private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) { 7175 return getColors(p).getPrimaryAccentColor(); 7176 } 7177 7178 /** 7179 * Apply the unstyled operations and return a new {@link Notification} object. 7180 * @hide 7181 */ 7182 @NonNull 7183 public Notification buildUnstyled() { 7184 if (mActions.size() > 0) { 7185 mN.actions = new Action[mActions.size()]; 7186 mActions.toArray(mN.actions); 7187 } 7188 if (!mPersonList.isEmpty()) { 7189 mN.extras.putParcelableArrayList(EXTRA_PEOPLE_LIST, mPersonList); 7190 } 7191 if (mN.bigContentView != null || mN.contentView != null 7192 || mN.headsUpContentView != null) { 7193 mN.extras.putBoolean(EXTRA_CONTAINS_CUSTOM_VIEW, true); 7194 } 7195 return mN; 7196 } 7197 7198 /** 7199 * Creates a Builder from an existing notification so further changes can be made. 7200 * @param context The context for your application / activity. 7201 * @param n The notification to create a Builder from. 7202 */ 7203 @NonNull recoverBuilder(Context context, Notification n)7204 public static Notification.Builder recoverBuilder(Context context, Notification n) { 7205 Trace.beginSection("Notification.Builder#recoverBuilder"); 7206 7207 try { 7208 // Re-create notification context so we can access app resources. 7209 ApplicationInfo applicationInfo = n.extras.getParcelable( 7210 EXTRA_BUILDER_APPLICATION_INFO, ApplicationInfo.class); 7211 Context builderContext; 7212 if (applicationInfo != null) { 7213 try { 7214 builderContext = context.createApplicationContext(applicationInfo, 7215 Context.CONTEXT_RESTRICTED); 7216 } catch (NameNotFoundException e) { 7217 Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found"); 7218 builderContext = context; // try with our context 7219 } 7220 } else { 7221 builderContext = context; // try with given context 7222 } 7223 7224 return new Builder(builderContext, n); 7225 } finally { 7226 Trace.endSection(); 7227 } 7228 } 7229 7230 /** 7231 * Determines whether the platform can generate contextual actions for a notification. 7232 * By default this is true. 7233 */ 7234 @NonNull setAllowSystemGeneratedContextualActions(boolean allowed)7235 public Builder setAllowSystemGeneratedContextualActions(boolean allowed) { 7236 mN.mAllowSystemGeneratedContextualActions = allowed; 7237 return this; 7238 } 7239 7240 /** 7241 * @deprecated Use {@link #build()} instead. 7242 */ 7243 @Deprecated getNotification()7244 public Notification getNotification() { 7245 return build(); 7246 } 7247 7248 /** 7249 * Combine all of the options that have been set and return a new {@link Notification} 7250 * object. 7251 * 7252 * If this notification has {@link BubbleMetadata} attached that was created with 7253 * a shortcutId a check will be performed to ensure the shortcutId supplied to bubble 7254 * metadata matches the shortcutId set on the notification builder, if one was set. 7255 * If the shortcutId's were specified but do not match, an exception is thrown here. 7256 * 7257 * @see BubbleMetadata.Builder#Builder(String) 7258 * @see #setShortcutId(String) 7259 */ 7260 @NonNull build()7261 public Notification build() { 7262 // Check shortcut id matches 7263 if (mN.mShortcutId != null 7264 && mN.mBubbleMetadata != null 7265 && mN.mBubbleMetadata.getShortcutId() != null 7266 && !mN.mShortcutId.equals(mN.mBubbleMetadata.getShortcutId())) { 7267 throw new IllegalArgumentException( 7268 "Notification and BubbleMetadata shortcut id's don't match," 7269 + " notification: " + mN.mShortcutId 7270 + " vs bubble: " + mN.mBubbleMetadata.getShortcutId()); 7271 } 7272 7273 // Adds any new extras provided by the user. 7274 if (mUserExtras != null) { 7275 final Bundle saveExtras = (Bundle) mUserExtras.clone(); 7276 mN.extras.putAll(saveExtras); 7277 } 7278 7279 if (!Flags.sortSectionByTime()) { 7280 mN.creationTime = System.currentTimeMillis(); 7281 } 7282 7283 // lazy stuff from mContext; see comment in Builder(Context, Notification) 7284 Notification.addFieldsFromContext(mContext, mN); 7285 7286 buildUnstyled(); 7287 7288 if (mStyle != null) { 7289 mStyle.reduceImageSizes(mContext); 7290 mStyle.purgeResources(); 7291 mStyle.validate(mContext); 7292 mStyle.buildStyled(mN); 7293 } 7294 mN.reduceImageSizes(mContext); 7295 7296 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N 7297 && !styleDisplaysCustomViewInline()) { 7298 RemoteViews newContentView = mN.contentView; 7299 RemoteViews newBigContentView = mN.bigContentView; 7300 RemoteViews newHeadsUpContentView = mN.headsUpContentView; 7301 if (newContentView == null) { 7302 newContentView = createContentView(); 7303 mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, 7304 newContentView.getSequenceNumber()); 7305 } 7306 if (newBigContentView == null) { 7307 newBigContentView = createBigContentView(); 7308 if (newBigContentView != null) { 7309 mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, 7310 newBigContentView.getSequenceNumber()); 7311 } 7312 } 7313 if (newHeadsUpContentView == null) { 7314 newHeadsUpContentView = createHeadsUpContentView(); 7315 if (newHeadsUpContentView != null) { 7316 mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, 7317 newHeadsUpContentView.getSequenceNumber()); 7318 } 7319 } 7320 // Don't set any of the content views until after they have all been generated, 7321 // to avoid the generated .contentView triggering the logic which skips generating 7322 // the .bigContentView. 7323 mN.contentView = newContentView; 7324 mN.bigContentView = newBigContentView; 7325 mN.headsUpContentView = newHeadsUpContentView; 7326 } 7327 7328 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { 7329 mN.flags |= FLAG_SHOW_LIGHTS; 7330 } 7331 7332 mN.allPendingIntents = null; 7333 7334 return mN; 7335 } 7336 styleDisplaysCustomViewInline()7337 private boolean styleDisplaysCustomViewInline() { 7338 return mStyle != null && mStyle.displayCustomViewInline(); 7339 } 7340 7341 /** 7342 * Apply this Builder to an existing {@link Notification} object. 7343 * 7344 * @hide 7345 */ 7346 @NonNull buildInto(@onNull Notification n)7347 public Notification buildInto(@NonNull Notification n) { 7348 build().cloneInto(n, true); 7349 return n; 7350 } 7351 7352 /** 7353 * Removes RemoteViews that were created for compatibility from {@param n}, if they did not 7354 * change. 7355 * 7356 * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}. 7357 * 7358 * @hide 7359 */ maybeCloneStrippedForDelivery(Notification n)7360 public static Notification maybeCloneStrippedForDelivery(Notification n) { 7361 String templateClass = n.extras.getString(EXTRA_TEMPLATE); 7362 7363 // Only strip views for known Styles because we won't know how to 7364 // re-create them otherwise. 7365 if (!TextUtils.isEmpty(templateClass) 7366 && getNotificationStyleClass(templateClass) == null) { 7367 return n; 7368 } 7369 7370 // Only strip unmodified BuilderRemoteViews. 7371 boolean stripContentView = n.contentView instanceof BuilderRemoteViews && 7372 n.extras.getInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT, -1) == 7373 n.contentView.getSequenceNumber(); 7374 boolean stripBigContentView = n.bigContentView instanceof BuilderRemoteViews && 7375 n.extras.getInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT, -1) == 7376 n.bigContentView.getSequenceNumber(); 7377 boolean stripHeadsUpContentView = n.headsUpContentView instanceof BuilderRemoteViews && 7378 n.extras.getInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT, -1) == 7379 n.headsUpContentView.getSequenceNumber(); 7380 7381 // Nothing to do here, no need to clone. 7382 if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) { 7383 return n; 7384 } 7385 7386 Notification clone = n.clone(); 7387 if (stripContentView) { 7388 clone.contentView = null; 7389 clone.extras.remove(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT); 7390 } 7391 if (stripBigContentView) { 7392 clone.bigContentView = null; 7393 clone.extras.remove(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT); 7394 } 7395 if (stripHeadsUpContentView) { 7396 clone.headsUpContentView = null; 7397 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT); 7398 } 7399 return clone; 7400 } 7401 7402 @UnsupportedAppUsage getBaseLayoutResource()7403 private int getBaseLayoutResource() { 7404 return R.layout.notification_template_material_base; 7405 } 7406 getHeadsUpBaseLayoutResource()7407 private int getHeadsUpBaseLayoutResource() { 7408 return R.layout.notification_template_material_heads_up_base; 7409 } 7410 getCompactHeadsUpBaseLayoutResource()7411 private int getCompactHeadsUpBaseLayoutResource() { 7412 return R.layout.notification_template_material_compact_heads_up_base; 7413 } 7414 getMessagingCompactHeadsUpLayoutResource()7415 private int getMessagingCompactHeadsUpLayoutResource() { 7416 return R.layout.notification_template_material_messaging_compact_heads_up; 7417 } 7418 getBigBaseLayoutResource()7419 private int getBigBaseLayoutResource() { 7420 return R.layout.notification_template_material_big_base; 7421 } 7422 getBigPictureLayoutResource()7423 private int getBigPictureLayoutResource() { 7424 return R.layout.notification_template_material_big_picture; 7425 } 7426 getBigTextLayoutResource()7427 private int getBigTextLayoutResource() { 7428 return R.layout.notification_template_material_big_text; 7429 } 7430 getInboxLayoutResource()7431 private int getInboxLayoutResource() { 7432 return R.layout.notification_template_material_inbox; 7433 } 7434 getMessagingLayoutResource()7435 private int getMessagingLayoutResource() { 7436 return R.layout.notification_template_material_messaging; 7437 } 7438 getBigMessagingLayoutResource()7439 private int getBigMessagingLayoutResource() { 7440 return R.layout.notification_template_material_big_messaging; 7441 } 7442 getConversationLayoutResource()7443 private int getConversationLayoutResource() { 7444 return R.layout.notification_template_material_conversation; 7445 } 7446 getActionLayoutResource()7447 private int getActionLayoutResource() { 7448 return R.layout.notification_material_action; 7449 } 7450 getEmphasizedActionLayoutResource()7451 private int getEmphasizedActionLayoutResource() { 7452 return R.layout.notification_material_action_emphasized; 7453 } 7454 getEmphasizedTombstoneActionLayoutResource()7455 private int getEmphasizedTombstoneActionLayoutResource() { 7456 return R.layout.notification_material_action_emphasized_tombstone; 7457 } 7458 getActionTombstoneLayoutResource()7459 private int getActionTombstoneLayoutResource() { 7460 return R.layout.notification_material_action_tombstone; 7461 } 7462 getBackgroundColor(StandardTemplateParams p)7463 private @ColorInt int getBackgroundColor(StandardTemplateParams p) { 7464 return getColors(p).getBackgroundColor(); 7465 } 7466 textColorsNeedInversion()7467 private boolean textColorsNeedInversion() { 7468 if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) { 7469 return false; 7470 } 7471 int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; 7472 return targetSdkVersion > Build.VERSION_CODES.M 7473 && targetSdkVersion < Build.VERSION_CODES.O; 7474 } 7475 7476 /** 7477 * Get the text that should be displayed in the statusBar when heads upped. This is 7478 * usually just the app name, but may be different depending on the style. 7479 * 7480 * @param publicMode If true, return a text that is safe to display in public. 7481 * 7482 * @hide 7483 */ getHeadsUpStatusBarText(boolean publicMode)7484 public CharSequence getHeadsUpStatusBarText(boolean publicMode) { 7485 if (mStyle != null && !publicMode) { 7486 CharSequence text = mStyle.getHeadsUpStatusBarText(); 7487 if (!TextUtils.isEmpty(text)) { 7488 return text; 7489 } 7490 } 7491 return loadHeaderAppName(); 7492 } 7493 7494 /** 7495 * @return if this builder uses a template 7496 * 7497 * @hide 7498 */ usesTemplate()7499 public boolean usesTemplate() { 7500 return (mN.contentView == null && mN.headsUpContentView == null 7501 && mN.bigContentView == null) 7502 || styleDisplaysCustomViewInline(); 7503 } 7504 } 7505 7506 /** 7507 * Reduces the image sizes to conform to a maximum allowed size. This also processes all custom 7508 * remote views. 7509 * 7510 * @hide 7511 */ reduceImageSizes(Context context)7512 void reduceImageSizes(Context context) { 7513 if (extras.getBoolean(EXTRA_REDUCED_IMAGES)) { 7514 return; 7515 } 7516 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 7517 7518 if (mSmallIcon != null 7519 // Only bitmap icons can be downscaled. 7520 && (mSmallIcon.getType() == Icon.TYPE_BITMAP 7521 || mSmallIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) { 7522 Resources resources = context.getResources(); 7523 int maxSize = resources.getDimensionPixelSize( 7524 isLowRam ? R.dimen.notification_small_icon_size_low_ram 7525 : R.dimen.notification_small_icon_size); 7526 mSmallIcon.scaleDownIfNecessary(maxSize, maxSize); 7527 } 7528 7529 if (mLargeIcon != null || largeIcon != null) { 7530 Resources resources = context.getResources(); 7531 Class<? extends Style> style = getNotificationStyle(); 7532 int maxSize = resources.getDimensionPixelSize(isLowRam 7533 ? R.dimen.notification_right_icon_size_low_ram 7534 : R.dimen.notification_right_icon_size); 7535 if (mLargeIcon != null) { 7536 mLargeIcon.scaleDownIfNecessary(maxSize, maxSize); 7537 } 7538 if (largeIcon != null) { 7539 largeIcon = Icon.scaleDownIfNecessary(largeIcon, maxSize, maxSize); 7540 } 7541 } 7542 reduceImageSizesForRemoteView(contentView, context, isLowRam); 7543 reduceImageSizesForRemoteView(headsUpContentView, context, isLowRam); 7544 reduceImageSizesForRemoteView(bigContentView, context, isLowRam); 7545 extras.putBoolean(EXTRA_REDUCED_IMAGES, true); 7546 } 7547 reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, boolean isLowRam)7548 private void reduceImageSizesForRemoteView(RemoteViews remoteView, Context context, 7549 boolean isLowRam) { 7550 if (remoteView != null) { 7551 Resources resources = context.getResources(); 7552 int maxWidth = resources.getDimensionPixelSize(isLowRam 7553 ? R.dimen.notification_custom_view_max_image_width_low_ram 7554 : R.dimen.notification_custom_view_max_image_width); 7555 int maxHeight = resources.getDimensionPixelSize(isLowRam 7556 ? R.dimen.notification_custom_view_max_image_height_low_ram 7557 : R.dimen.notification_custom_view_max_image_height); 7558 remoteView.reduceImageSizes(maxWidth, maxHeight); 7559 } 7560 } 7561 7562 /** 7563 * @return whether this notification is a foreground service notification 7564 * @hide 7565 */ isForegroundService()7566 public boolean isForegroundService() { 7567 return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; 7568 } 7569 7570 /** 7571 * @return whether this notification is associated with a user initiated job 7572 * @hide 7573 */ 7574 @TestApi isUserInitiatedJob()7575 public boolean isUserInitiatedJob() { 7576 return (flags & Notification.FLAG_USER_INITIATED_JOB) != 0; 7577 } 7578 7579 /** 7580 * @return whether this notification is associated with either a foreground service or 7581 * a user initiated job 7582 * @hide 7583 */ isFgsOrUij()7584 public boolean isFgsOrUij() { 7585 return isForegroundService() || isUserInitiatedJob(); 7586 } 7587 7588 /** 7589 * Describe whether this notification's content such that it should always display 7590 * immediately when tied to a foreground service, even if the system might generally 7591 * avoid showing the notifications for short-lived foreground service lifetimes. 7592 * 7593 * Immediate visibility of the Notification is indicated when: 7594 * <ul> 7595 * <li>The app specifically indicated it with 7596 * {@link Notification.Builder#setForegroundServiceBehavior(int) 7597 * setForegroundServiceBehavior(BEHAVIOR_IMMEDIATE_DISPLAY)}</li> 7598 * <li>It is a media notification or has an associated media session</li> 7599 * <li>It is a call or navigation notification</li> 7600 * <li>It provides additional action affordances</li> 7601 * </ul> 7602 * 7603 * If the app has specified 7604 * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)} 7605 * then this method will return {@code false} and notification visibility will be 7606 * deferred following the service's transition to the foreground state even in the 7607 * circumstances described above. 7608 * 7609 * @return whether this notification should be displayed immediately when 7610 * its associated service transitions to the foreground state 7611 * @hide 7612 */ 7613 @TestApi shouldShowForegroundImmediately()7614 public boolean shouldShowForegroundImmediately() { 7615 // Has the app demanded immediate display? 7616 if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) { 7617 return true; 7618 } 7619 7620 // Has the app demanded deferred display? 7621 if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) { 7622 return false; 7623 } 7624 7625 // We show these sorts of notifications immediately in the absence of 7626 // any explicit app declaration 7627 if (isMediaNotification() 7628 || CATEGORY_CALL.equals(category) 7629 || CATEGORY_NAVIGATION.equals(category) 7630 || (actions != null && actions.length > 0)) { 7631 return true; 7632 } 7633 7634 // No extenuating circumstances: defer visibility 7635 return false; 7636 } 7637 7638 /** 7639 * Has forced deferral for FGS purposes been specified? 7640 * @hide 7641 */ isForegroundDisplayForceDeferred()7642 public boolean isForegroundDisplayForceDeferred() { 7643 return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior; 7644 } 7645 7646 /** 7647 * @return the style class of this notification 7648 * @hide 7649 */ getNotificationStyle()7650 public Class<? extends Notification.Style> getNotificationStyle() { 7651 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 7652 7653 if (!TextUtils.isEmpty(templateClass)) { 7654 return Notification.getNotificationStyleClass(templateClass); 7655 } 7656 return null; 7657 } 7658 7659 /** 7660 * @return whether the style of this notification is the one provided 7661 * @hide 7662 */ isStyle(@onNull Class<? extends Style> styleClass)7663 public boolean isStyle(@NonNull Class<? extends Style> styleClass) { 7664 String templateClass = extras.getString(Notification.EXTRA_TEMPLATE); 7665 return Objects.equals(templateClass, styleClass.getName()); 7666 } 7667 7668 /** 7669 * @return true if this notification is colorized *for the purposes of ranking*. If the 7670 * {@link #color} is {@link #COLOR_DEFAULT} this will be true, even though the actual 7671 * appearance of the notification may not be "colorized". 7672 * 7673 * @hide 7674 */ isColorized()7675 public boolean isColorized() { 7676 return extras.getBoolean(EXTRA_COLORIZED) 7677 && (hasColorizedPermission() || isFgsOrUij()); 7678 } 7679 7680 /** 7681 * Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS 7682 * permission. The permission is checked when a notification is enqueued. 7683 * 7684 * @hide 7685 */ hasColorizedPermission()7686 public boolean hasColorizedPermission() { 7687 return (flags & Notification.FLAG_CAN_COLORIZE) != 0; 7688 } 7689 7690 /** 7691 * @return true if this is a media style notification with a media session 7692 * 7693 * @hide 7694 */ isMediaNotification()7695 public boolean isMediaNotification() { 7696 Class<? extends Style> style = getNotificationStyle(); 7697 boolean isMediaStyle = (MediaStyle.class.equals(style) 7698 || DecoratedMediaCustomViewStyle.class.equals(style)); 7699 7700 boolean hasMediaSession = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION, 7701 MediaSession.Token.class) != null; 7702 7703 return isMediaStyle && hasMediaSession; 7704 } 7705 7706 /** 7707 * @return true for custom notifications, including notifications 7708 * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle, 7709 * and other notifications with user-provided custom views. 7710 * 7711 * @hide 7712 */ isCustomNotification()7713 public Boolean isCustomNotification() { 7714 if (contentView == null 7715 && bigContentView == null 7716 && headsUpContentView == null) { 7717 return false; 7718 } 7719 return true; 7720 } 7721 7722 /** 7723 * @return true if this notification is showing as a bubble 7724 * 7725 * @hide 7726 */ isBubbleNotification()7727 public boolean isBubbleNotification() { 7728 return (flags & Notification.FLAG_BUBBLE) != 0; 7729 } 7730 hasLargeIcon()7731 private boolean hasLargeIcon() { 7732 return mLargeIcon != null || largeIcon != null; 7733 } 7734 7735 /** 7736 * Returns #when, unless it's set to 0, which should be shown as/treated as a 'current' 7737 * notification. 0 is treated as a special value because it was special in an old version of 7738 * android, and some apps are still (incorrectly) using it. 7739 * 7740 * @hide 7741 */ getWhen()7742 public long getWhen() { 7743 if (Flags.sortSectionByTime()) { 7744 if (when == 0) { 7745 return creationTime; 7746 } 7747 } 7748 return when; 7749 } 7750 7751 /** 7752 * @return true if the notification will show the time; false otherwise 7753 * @hide 7754 */ showsTime()7755 public boolean showsTime() { 7756 if (Flags.sortSectionByTime()) { 7757 return extras.getBoolean(EXTRA_SHOW_WHEN); 7758 } 7759 return when != 0 && extras.getBoolean(EXTRA_SHOW_WHEN); 7760 } 7761 7762 /** 7763 * @return true if the notification will show a chronometer; false otherwise 7764 * @hide 7765 */ showsChronometer()7766 public boolean showsChronometer() { 7767 if (Flags.sortSectionByTime()) { 7768 return extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 7769 } 7770 return when != 0 && extras.getBoolean(EXTRA_SHOW_CHRONOMETER); 7771 } 7772 7773 /** 7774 * @return true if the notification has image 7775 */ hasImage()7776 public boolean hasImage() { 7777 if (isStyle(MessagingStyle.class) && extras != null) { 7778 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, 7779 Parcelable.class); 7780 if (!ArrayUtils.isEmpty(messages)) { 7781 for (MessagingStyle.Message m : MessagingStyle.Message 7782 .getMessagesFromBundleArray(messages)) { 7783 if (m.getDataUri() != null 7784 && m.getDataMimeType() != null 7785 && m.getDataMimeType().startsWith("image/")) { 7786 return true; 7787 } 7788 } 7789 } 7790 } else if (hasLargeIcon()) { 7791 return true; 7792 } else if (extras.containsKey(EXTRA_BACKGROUND_IMAGE_URI)) { 7793 return true; 7794 } 7795 return false; 7796 } 7797 7798 7799 /** 7800 * @removed 7801 */ 7802 @SystemApi getNotificationStyleClass(String templateClass)7803 public static Class<? extends Style> getNotificationStyleClass(String templateClass) { 7804 for (Class<? extends Style> innerClass : PLATFORM_STYLE_CLASSES) { 7805 if (templateClass.equals(innerClass.getName())) { 7806 return innerClass; 7807 } 7808 } 7809 return null; 7810 } 7811 buildCustomContentIntoTemplate(@onNull Context context, @NonNull RemoteViews template, @Nullable RemoteViews customContent, @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result)7812 private static void buildCustomContentIntoTemplate(@NonNull Context context, 7813 @NonNull RemoteViews template, @Nullable RemoteViews customContent, 7814 @NonNull StandardTemplateParams p, @NonNull TemplateBindResult result) { 7815 int childIndex = -1; 7816 if (customContent != null) { 7817 // Need to clone customContent before adding, because otherwise it can no longer be 7818 // parceled independently of remoteViews. 7819 customContent = customContent.clone(); 7820 if (p.mHeaderless) { 7821 template.removeFromParent(R.id.notification_top_line); 7822 // We do not know how many lines ar emote view has, so we presume it has 2; this 7823 // ensures that we don't under-pad the content, which could lead to abuse, at the 7824 // cost of making single-line custom content over-padded. 7825 Builder.setHeaderlessVerticalMargins(template, p, true /* hasSecondLine */); 7826 } else { 7827 // also update the end margin to account for the large icon or expander 7828 Resources resources = context.getResources(); 7829 result.mTitleMarginSet.applyToView(template, R.id.notification_main_column, 7830 resources.getDimension(R.dimen.notification_content_margin_end) 7831 / resources.getDisplayMetrics().density); 7832 } 7833 template.removeAllViewsExceptId(R.id.notification_main_column, R.id.progress); 7834 template.addView(R.id.notification_main_column, customContent, 0 /* index */); 7835 template.addFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED); 7836 childIndex = 0; 7837 } 7838 template.setIntTag(R.id.notification_main_column, 7839 com.android.internal.R.id.notification_custom_view_index_tag, 7840 childIndex); 7841 } 7842 7843 /** 7844 * An object that can apply a rich notification style to a {@link Notification.Builder} 7845 * object. 7846 */ 7847 public static abstract class Style { 7848 7849 /** 7850 * @deprecated public access to the constructor of Style() is only useful for creating 7851 * custom subclasses, but that has actually been impossible due to hidden abstract 7852 * methods, so this constructor is now officially deprecated to clarify that this is 7853 * intended to be disallowed. 7854 */ 7855 @Deprecated Style()7856 public Style() {} 7857 7858 /** 7859 * The number of items allowed simulatanously in the remote input history. 7860 * @hide 7861 */ 7862 static final int MAX_REMOTE_INPUT_HISTORY_LINES = 3; 7863 private CharSequence mBigContentTitle; 7864 7865 /** 7866 * @hide 7867 */ 7868 protected CharSequence mSummaryText = null; 7869 7870 /** 7871 * @hide 7872 */ 7873 protected boolean mSummaryTextSet = false; 7874 7875 protected Builder mBuilder; 7876 7877 /** 7878 * Overrides ContentTitle in the big form of the template. 7879 * This defaults to the value passed to setContentTitle(). 7880 */ internalSetBigContentTitle(CharSequence title)7881 protected void internalSetBigContentTitle(CharSequence title) { 7882 mBigContentTitle = title; 7883 } 7884 7885 /** 7886 * Set the first line of text after the detail section in the big form of the template. 7887 */ internalSetSummaryText(CharSequence cs)7888 protected void internalSetSummaryText(CharSequence cs) { 7889 mSummaryText = cs; 7890 mSummaryTextSet = true; 7891 } 7892 setBuilder(Builder builder)7893 public void setBuilder(Builder builder) { 7894 if (mBuilder != builder) { 7895 mBuilder = builder; 7896 if (mBuilder != null) { 7897 mBuilder.setStyle(this); 7898 } 7899 } 7900 } 7901 checkBuilder()7902 protected void checkBuilder() { 7903 if (mBuilder == null) { 7904 throw new IllegalArgumentException("Style requires a valid Builder object"); 7905 } 7906 } 7907 getStandardView(int layoutId)7908 protected RemoteViews getStandardView(int layoutId) { 7909 // TODO(jeffdq): set the view type based on the layout resource? 7910 StandardTemplateParams p = mBuilder.mParams.reset() 7911 .viewType(StandardTemplateParams.VIEW_TYPE_UNSPECIFIED) 7912 .fillTextsFrom(mBuilder); 7913 return getStandardView(layoutId, p, null); 7914 } 7915 7916 7917 /** 7918 * Get the standard view for this style. 7919 * 7920 * @param layoutId The layout id to use. 7921 * @param p the params for this inflation. 7922 * @param result The result where template bind information is saved. 7923 * @return A remoteView for this style. 7924 * @hide 7925 */ getStandardView(int layoutId, StandardTemplateParams p, TemplateBindResult result)7926 protected RemoteViews getStandardView(int layoutId, StandardTemplateParams p, 7927 TemplateBindResult result) { 7928 checkBuilder(); 7929 7930 if (mBigContentTitle != null) { 7931 p.mTitle = mBigContentTitle; 7932 } 7933 7934 return mBuilder.applyStandardTemplateWithActions(layoutId, p, result); 7935 } 7936 7937 /** 7938 * Construct a Style-specific RemoteViews for the collapsed notification layout. 7939 * The default implementation has nothing additional to add. 7940 * 7941 * @param increasedHeight true if this layout be created with an increased height. 7942 * @hide 7943 */ makeContentView(boolean increasedHeight)7944 public RemoteViews makeContentView(boolean increasedHeight) { 7945 return null; 7946 } 7947 7948 /** 7949 * Construct a Style-specific RemoteViews for the final big notification layout. 7950 * @hide 7951 */ makeBigContentView()7952 public RemoteViews makeBigContentView() { 7953 return null; 7954 } 7955 7956 /** 7957 * Construct a Style-specific RemoteViews for the final HUN layout. 7958 * 7959 * @param increasedHeight true if this layout be created with an increased height. 7960 * @hide 7961 */ makeHeadsUpContentView(boolean increasedHeight)7962 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 7963 return null; 7964 } 7965 7966 /** 7967 * Construct a Style-specific RemoteViews for the final compact HUN layout. 7968 * return null to use the standard compact heads up view. 7969 * @hide 7970 */ 7971 @Nullable makeCompactHeadsUpContentView()7972 public RemoteViews makeCompactHeadsUpContentView() { 7973 return null; 7974 } 7975 7976 /** 7977 * Apply any style-specific extras to this notification before shipping it out. 7978 * @hide 7979 */ addExtras(Bundle extras)7980 public void addExtras(Bundle extras) { 7981 if (mSummaryTextSet) { 7982 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 7983 } 7984 if (mBigContentTitle != null) { 7985 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 7986 } 7987 extras.putString(EXTRA_TEMPLATE, this.getClass().getName()); 7988 } 7989 7990 /** 7991 * Reconstruct the internal state of this Style object from extras. 7992 * @hide 7993 */ restoreFromExtras(Bundle extras)7994 protected void restoreFromExtras(Bundle extras) { 7995 if (extras.containsKey(EXTRA_SUMMARY_TEXT)) { 7996 mSummaryText = extras.getCharSequence(EXTRA_SUMMARY_TEXT); 7997 mSummaryTextSet = true; 7998 } 7999 if (extras.containsKey(EXTRA_TITLE_BIG)) { 8000 mBigContentTitle = extras.getCharSequence(EXTRA_TITLE_BIG); 8001 } 8002 } 8003 8004 8005 /** 8006 * @hide 8007 */ buildStyled(Notification wip)8008 public Notification buildStyled(Notification wip) { 8009 addExtras(wip.extras); 8010 return wip; 8011 } 8012 8013 /** 8014 * @hide 8015 */ purgeResources()8016 public void purgeResources() {} 8017 8018 /** 8019 * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is 8020 * attached to. 8021 * <p> 8022 * Note: Calling build() multiple times returns the same Notification instance, 8023 * so reusing a builder to create multiple Notifications is discouraged. 8024 * 8025 * @return the fully constructed Notification. 8026 */ build()8027 public Notification build() { 8028 checkBuilder(); 8029 return mBuilder.build(); 8030 } 8031 8032 /** 8033 * @hide 8034 * @return Whether we should put the summary be put into the notification header 8035 */ hasSummaryInHeader()8036 public boolean hasSummaryInHeader() { 8037 return true; 8038 } 8039 8040 /** 8041 * @hide 8042 * @return Whether custom content views are displayed inline in the style 8043 */ displayCustomViewInline()8044 public boolean displayCustomViewInline() { 8045 return false; 8046 } 8047 8048 /** 8049 * Reduces the image sizes contained in this style. 8050 * 8051 * @hide 8052 */ reduceImageSizes(Context context)8053 public void reduceImageSizes(Context context) { 8054 } 8055 8056 /** 8057 * Validate that this style was properly composed. This is called at build time. 8058 * @hide 8059 */ validate(Context context)8060 public void validate(Context context) { 8061 } 8062 8063 /** 8064 * @hide 8065 */ 8066 @SuppressWarnings("HiddenAbstractMethod") areNotificationsVisiblyDifferent(Style other)8067 public abstract boolean areNotificationsVisiblyDifferent(Style other); 8068 8069 /** 8070 * @return the text that should be displayed in the statusBar when heads-upped. 8071 * If {@code null} is returned, the default implementation will be used. 8072 * 8073 * @hide 8074 */ getHeadsUpStatusBarText()8075 public CharSequence getHeadsUpStatusBarText() { 8076 return null; 8077 } 8078 } 8079 8080 /** 8081 * Helper class for generating large-format notifications that include a large image attachment. 8082 * 8083 * Here's how you'd set the <code>BigPictureStyle</code> on a notification: 8084 * <pre class="prettyprint"> 8085 * Notification notif = new Notification.Builder(mContext) 8086 * .setContentTitle("New photo from " + sender.toString()) 8087 * .setContentText(subject) 8088 * .setSmallIcon(R.drawable.new_post) 8089 * .setLargeIcon(aBitmap) 8090 * .setStyle(new Notification.BigPictureStyle() 8091 * .bigPicture(aBigBitmap)) 8092 * .build(); 8093 * </pre> 8094 * 8095 * @see Notification#bigContentView 8096 */ 8097 public static class BigPictureStyle extends Style { 8098 private Icon mPictureIcon; 8099 private Icon mBigLargeIcon; 8100 private boolean mBigLargeIconSet = false; 8101 private CharSequence mPictureContentDescription; 8102 private boolean mShowBigPictureWhenCollapsed; 8103 BigPictureStyle()8104 public BigPictureStyle() { 8105 } 8106 8107 /** 8108 * @deprecated use {@code BigPictureStyle()}. 8109 */ 8110 @Deprecated BigPictureStyle(Builder builder)8111 public BigPictureStyle(Builder builder) { 8112 setBuilder(builder); 8113 } 8114 8115 /** 8116 * Overrides ContentTitle in the big form of the template. 8117 * This defaults to the value passed to setContentTitle(). 8118 */ 8119 @NonNull setBigContentTitle(@ullable CharSequence title)8120 public BigPictureStyle setBigContentTitle(@Nullable CharSequence title) { 8121 internalSetBigContentTitle(safeCharSequence(title)); 8122 return this; 8123 } 8124 8125 /** 8126 * Set the first line of text after the detail section in the big form of the template. 8127 */ 8128 @NonNull setSummaryText(@ullable CharSequence cs)8129 public BigPictureStyle setSummaryText(@Nullable CharSequence cs) { 8130 internalSetSummaryText(safeCharSequence(cs)); 8131 return this; 8132 } 8133 8134 /** 8135 * Set the content description of the big picture. 8136 */ 8137 @NonNull setContentDescription( @ullable CharSequence contentDescription)8138 public BigPictureStyle setContentDescription( 8139 @Nullable CharSequence contentDescription) { 8140 mPictureContentDescription = contentDescription; 8141 return this; 8142 } 8143 8144 /** 8145 * @hide 8146 */ 8147 @Nullable getBigPicture()8148 public Icon getBigPicture() { 8149 if (mPictureIcon != null) { 8150 return mPictureIcon; 8151 } 8152 return null; 8153 } 8154 8155 /** 8156 * Provide the bitmap to be used as the payload for the BigPicture notification. 8157 */ 8158 @NonNull bigPicture(@ullable Bitmap b)8159 public BigPictureStyle bigPicture(@Nullable Bitmap b) { 8160 mPictureIcon = b == null ? null : Icon.createWithBitmap(b); 8161 return this; 8162 } 8163 8164 /** 8165 * Provide the content Uri to be used as the payload for the BigPicture notification. 8166 */ 8167 @NonNull bigPicture(@ullable Icon icon)8168 public BigPictureStyle bigPicture(@Nullable Icon icon) { 8169 mPictureIcon = icon; 8170 return this; 8171 } 8172 8173 /** 8174 * When set, the {@link #bigPicture(Bitmap) big picture} of this style will be promoted and 8175 * shown in place of the {@link Builder#setLargeIcon(Icon) large icon} in the collapsed 8176 * state of this notification. 8177 */ 8178 @NonNull showBigPictureWhenCollapsed(boolean show)8179 public BigPictureStyle showBigPictureWhenCollapsed(boolean show) { 8180 mShowBigPictureWhenCollapsed = show; 8181 return this; 8182 } 8183 8184 /** 8185 * Override the large icon when the big notification is shown. 8186 */ 8187 @NonNull bigLargeIcon(@ullable Bitmap b)8188 public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) { 8189 return bigLargeIcon(b != null ? Icon.createWithBitmap(b) : null); 8190 } 8191 8192 /** 8193 * Override the large icon when the big notification is shown. 8194 */ 8195 @NonNull bigLargeIcon(@ullable Icon icon)8196 public BigPictureStyle bigLargeIcon(@Nullable Icon icon) { 8197 mBigLargeIconSet = true; 8198 mBigLargeIcon = icon; 8199 return this; 8200 } 8201 8202 /** @hide */ 8203 public static final int MIN_ASHMEM_BITMAP_SIZE = 128 * (1 << 10); 8204 8205 /** 8206 * @hide 8207 */ 8208 @Override purgeResources()8209 public void purgeResources() { 8210 super.purgeResources(); 8211 if (mPictureIcon != null) { 8212 mPictureIcon.convertToAshmem(); 8213 } 8214 if (mBigLargeIcon != null) { 8215 mBigLargeIcon.convertToAshmem(); 8216 } 8217 } 8218 8219 /** 8220 * @hide 8221 */ 8222 @Override reduceImageSizes(Context context)8223 public void reduceImageSizes(Context context) { 8224 super.reduceImageSizes(context); 8225 Resources resources = context.getResources(); 8226 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 8227 if (mPictureIcon != null) { 8228 int maxPictureHeight = resources.getDimensionPixelSize(isLowRam 8229 ? R.dimen.notification_big_picture_max_height_low_ram 8230 : R.dimen.notification_big_picture_max_height); 8231 int maxPictureWidth = resources.getDimensionPixelSize(isLowRam 8232 ? R.dimen.notification_big_picture_max_width_low_ram 8233 : R.dimen.notification_big_picture_max_width); 8234 mPictureIcon.scaleDownIfNecessary(maxPictureWidth, maxPictureHeight); 8235 } 8236 if (mBigLargeIcon != null) { 8237 int rightIconSize = resources.getDimensionPixelSize(isLowRam 8238 ? R.dimen.notification_right_icon_size_low_ram 8239 : R.dimen.notification_right_icon_size); 8240 mBigLargeIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 8241 } 8242 } 8243 8244 /** 8245 * @hide 8246 */ 8247 @Override makeContentView(boolean increasedHeight)8248 public RemoteViews makeContentView(boolean increasedHeight) { 8249 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 8250 return super.makeContentView(increasedHeight); 8251 } 8252 8253 StandardTemplateParams p = mBuilder.mParams.reset() 8254 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 8255 .fillTextsFrom(mBuilder) 8256 .promotedPicture(mPictureIcon); 8257 return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */); 8258 } 8259 8260 /** 8261 * @hide 8262 */ 8263 @Override makeHeadsUpContentView(boolean increasedHeight)8264 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8265 if (mPictureIcon == null || !mShowBigPictureWhenCollapsed) { 8266 return super.makeHeadsUpContentView(increasedHeight); 8267 } 8268 8269 StandardTemplateParams p = mBuilder.mParams.reset() 8270 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 8271 .fillTextsFrom(mBuilder) 8272 .promotedPicture(mPictureIcon); 8273 return getStandardView(mBuilder.getHeadsUpBaseLayoutResource(), p, null /* result */); 8274 } 8275 8276 /** 8277 * @hide 8278 */ makeBigContentView()8279 public RemoteViews makeBigContentView() { 8280 // Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet 8281 // This covers the following cases: 8282 // 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides 8283 // mN.mLargeIcon 8284 // 2. !mBigLargeIconSet -> mN.mLargeIcon applies 8285 Icon oldLargeIcon = null; 8286 Bitmap largeIconLegacy = null; 8287 if (mBigLargeIconSet) { 8288 oldLargeIcon = mBuilder.mN.mLargeIcon; 8289 mBuilder.mN.mLargeIcon = mBigLargeIcon; 8290 // The legacy largeIcon might not allow us to clear the image, as it's taken in 8291 // replacement if the other one is null. Because we're restoring these legacy icons 8292 // for old listeners, this is in general non-null. 8293 largeIconLegacy = mBuilder.mN.largeIcon; 8294 mBuilder.mN.largeIcon = null; 8295 } 8296 8297 StandardTemplateParams p = mBuilder.mParams.reset() 8298 .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder); 8299 RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(), 8300 p, null /* result */); 8301 if (mSummaryTextSet) { 8302 contentView.setTextViewText(R.id.text, 8303 mBuilder.ensureColorSpanContrastOrStripStyling( 8304 mBuilder.processLegacyText(mSummaryText), p)); 8305 mBuilder.setTextViewColorSecondary(contentView, R.id.text, p); 8306 contentView.setViewVisibility(R.id.text, View.VISIBLE); 8307 } 8308 8309 if (mBigLargeIconSet) { 8310 mBuilder.mN.mLargeIcon = oldLargeIcon; 8311 mBuilder.mN.largeIcon = largeIconLegacy; 8312 } 8313 8314 contentView.setImageViewIcon(R.id.big_picture, mPictureIcon); 8315 8316 if (mPictureContentDescription != null) { 8317 contentView.setContentDescription(R.id.big_picture, mPictureContentDescription); 8318 } 8319 8320 return contentView; 8321 } 8322 8323 /** 8324 * @hide 8325 */ addExtras(Bundle extras)8326 public void addExtras(Bundle extras) { 8327 super.addExtras(extras); 8328 8329 if (mBigLargeIconSet) { 8330 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 8331 } 8332 if (mPictureContentDescription != null) { 8333 extras.putCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION, 8334 mPictureContentDescription); 8335 } 8336 extras.putBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED, mShowBigPictureWhenCollapsed); 8337 8338 if (mPictureIcon == null) { 8339 extras.remove(EXTRA_PICTURE_ICON); 8340 extras.remove(EXTRA_PICTURE); 8341 } else if (mPictureIcon.getType() == Icon.TYPE_BITMAP) { 8342 // If the icon contains a bitmap, use the old extra so that listeners which look 8343 // for that extra can still find the picture. Don't include the new extra in 8344 // that case, to avoid duplicating data. Leave the unused extra set to null to avoid 8345 // crashing apps that came to expect it to be present but null. 8346 extras.putParcelable(EXTRA_PICTURE, mPictureIcon.getBitmap()); 8347 extras.putParcelable(EXTRA_PICTURE_ICON, null); 8348 } else { 8349 extras.putParcelable(EXTRA_PICTURE, null); 8350 extras.putParcelable(EXTRA_PICTURE_ICON, mPictureIcon); 8351 } 8352 } 8353 8354 /** 8355 * @hide 8356 */ 8357 @Override restoreFromExtras(Bundle extras)8358 protected void restoreFromExtras(Bundle extras) { 8359 super.restoreFromExtras(extras); 8360 8361 if (extras.containsKey(EXTRA_LARGE_ICON_BIG)) { 8362 mBigLargeIconSet = true; 8363 mBigLargeIcon = extras.getParcelable(EXTRA_LARGE_ICON_BIG, Icon.class); 8364 } 8365 8366 if (extras.containsKey(EXTRA_PICTURE_CONTENT_DESCRIPTION)) { 8367 mPictureContentDescription = 8368 extras.getCharSequence(EXTRA_PICTURE_CONTENT_DESCRIPTION); 8369 } 8370 8371 mShowBigPictureWhenCollapsed = extras.getBoolean(EXTRA_SHOW_BIG_PICTURE_WHEN_COLLAPSED); 8372 8373 mPictureIcon = getPictureIcon(extras); 8374 } 8375 8376 /** @hide */ 8377 @Nullable getPictureIcon(@ullable Bundle extras)8378 public static Icon getPictureIcon(@Nullable Bundle extras) { 8379 if (extras == null) return null; 8380 // When this style adds a picture, we only add one of the keys. If both were added, 8381 // it would most likely be a legacy app trying to override the picture in some way. 8382 // Because of that case it's better to give precedence to the legacy field. 8383 Bitmap bitmapPicture = extras.getParcelable(EXTRA_PICTURE, Bitmap.class); 8384 if (bitmapPicture != null) { 8385 return Icon.createWithBitmap(bitmapPicture); 8386 } else { 8387 return extras.getParcelable(EXTRA_PICTURE_ICON, Icon.class); 8388 } 8389 } 8390 8391 /** 8392 * @hide 8393 */ 8394 @Override hasSummaryInHeader()8395 public boolean hasSummaryInHeader() { 8396 return false; 8397 } 8398 8399 /** 8400 * @hide 8401 */ 8402 @Override areNotificationsVisiblyDifferent(Style other)8403 public boolean areNotificationsVisiblyDifferent(Style other) { 8404 if (other == null || getClass() != other.getClass()) { 8405 return true; 8406 } 8407 BigPictureStyle otherS = (BigPictureStyle) other; 8408 return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture()); 8409 } 8410 } 8411 8412 /** 8413 * Helper class for generating large-format notifications that include a lot of text. 8414 * 8415 * Here's how you'd set the <code>BigTextStyle</code> on a notification: 8416 * <pre class="prettyprint"> 8417 * Notification notif = new Notification.Builder(mContext) 8418 * .setContentTitle("New mail from " + sender.toString()) 8419 * .setContentText(subject) 8420 * .setSmallIcon(R.drawable.new_mail) 8421 * .setLargeIcon(aBitmap) 8422 * .setStyle(new Notification.BigTextStyle() 8423 * .bigText(aVeryLongString)) 8424 * .build(); 8425 * </pre> 8426 * 8427 * @see Notification#bigContentView 8428 */ 8429 public static class BigTextStyle extends Style { 8430 8431 private CharSequence mBigText; 8432 BigTextStyle()8433 public BigTextStyle() { 8434 } 8435 8436 /** 8437 * @deprecated use {@code BigTextStyle()}. 8438 */ 8439 @Deprecated BigTextStyle(Builder builder)8440 public BigTextStyle(Builder builder) { 8441 setBuilder(builder); 8442 } 8443 8444 /** 8445 * Overrides ContentTitle in the big form of the template. 8446 * This defaults to the value passed to setContentTitle(). 8447 */ setBigContentTitle(CharSequence title)8448 public BigTextStyle setBigContentTitle(CharSequence title) { 8449 internalSetBigContentTitle(safeCharSequence(title)); 8450 return this; 8451 } 8452 8453 /** 8454 * Set the first line of text after the detail section in the big form of the template. 8455 */ setSummaryText(CharSequence cs)8456 public BigTextStyle setSummaryText(CharSequence cs) { 8457 internalSetSummaryText(safeCharSequence(cs)); 8458 return this; 8459 } 8460 8461 /** 8462 * Provide the longer text to be displayed in the big form of the 8463 * template in place of the content text. 8464 */ bigText(CharSequence cs)8465 public BigTextStyle bigText(CharSequence cs) { 8466 mBigText = safeCharSequence(cs); 8467 return this; 8468 } 8469 8470 /** 8471 * @hide 8472 */ getBigText()8473 public CharSequence getBigText() { 8474 return mBigText; 8475 } 8476 8477 /** 8478 * @hide 8479 */ addExtras(Bundle extras)8480 public void addExtras(Bundle extras) { 8481 super.addExtras(extras); 8482 8483 extras.putCharSequence(EXTRA_BIG_TEXT, mBigText); 8484 } 8485 8486 /** 8487 * @hide 8488 */ 8489 @Override restoreFromExtras(Bundle extras)8490 protected void restoreFromExtras(Bundle extras) { 8491 super.restoreFromExtras(extras); 8492 8493 mBigText = extras.getCharSequence(EXTRA_BIG_TEXT); 8494 } 8495 8496 /** 8497 * @param increasedHeight true if this layout be created with an increased height. 8498 * 8499 * @hide 8500 */ 8501 @Override makeContentView(boolean increasedHeight)8502 public RemoteViews makeContentView(boolean increasedHeight) { 8503 if (increasedHeight) { 8504 ArrayList<Action> originalActions = mBuilder.mActions; 8505 mBuilder.mActions = new ArrayList<>(); 8506 RemoteViews remoteViews = makeBigContentView(); 8507 mBuilder.mActions = originalActions; 8508 return remoteViews; 8509 } 8510 return super.makeContentView(increasedHeight); 8511 } 8512 8513 /** 8514 * @hide 8515 */ 8516 @Override makeHeadsUpContentView(boolean increasedHeight)8517 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 8518 if (increasedHeight && mBuilder.mActions.size() > 0) { 8519 // TODO(b/163626038): pass VIEW_TYPE_HEADS_UP? 8520 return makeBigContentView(); 8521 } 8522 return super.makeHeadsUpContentView(increasedHeight); 8523 } 8524 8525 /** 8526 * @hide 8527 */ makeBigContentView()8528 public RemoteViews makeBigContentView() { 8529 StandardTemplateParams p = mBuilder.mParams.reset() 8530 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 8531 .allowTextWithProgress(true) 8532 .textViewId(R.id.big_text) 8533 .fillTextsFrom(mBuilder); 8534 8535 // Replace the text with the big text, but only if the big text is not empty. 8536 CharSequence bigTextText = mBuilder.processLegacyText(mBigText); 8537 if (Flags.cleanUpSpansAndNewLines()) { 8538 bigTextText = normalizeBigText(stripStyling(bigTextText)); 8539 } 8540 if (!TextUtils.isEmpty(bigTextText)) { 8541 p.text(bigTextText); 8542 } 8543 8544 return getStandardView(mBuilder.getBigTextLayoutResource(), p, null /* result */); 8545 } 8546 8547 /** 8548 * @hide 8549 * Spans are ignored when comparing text for visual difference. 8550 */ 8551 @Override areNotificationsVisiblyDifferent(Style other)8552 public boolean areNotificationsVisiblyDifferent(Style other) { 8553 if (other == null || getClass() != other.getClass()) { 8554 return true; 8555 } 8556 BigTextStyle newS = (BigTextStyle) other; 8557 return !Objects.equals(String.valueOf(getBigText()), String.valueOf(newS.getBigText())); 8558 } 8559 8560 } 8561 8562 /** 8563 * Helper class for generating large-format notifications that include multiple back-and-forth 8564 * messages of varying types between any number of people. 8565 * 8566 * <p> 8567 * If the platform does not provide large-format notifications, this method has no effect. The 8568 * user will always see the normal notification view. 8569 * 8570 * <p> 8571 * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is 8572 * required to use the {@link Person} class in order to get an optimal rendering of the 8573 * notification and its avatars. For conversations involving multiple people, the app should 8574 * also make sure that it marks the conversation as a group with 8575 * {@link #setGroupConversation(boolean)}. 8576 * 8577 * <p> 8578 * From Android {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, messaging style 8579 * notifications that are associated with a valid conversation shortcut 8580 * (via {@link Notification.Builder#setShortcutId(String)}) are displayed in a dedicated 8581 * conversation section in the shade above non-conversation alerting and silence notifications. 8582 * To be a valid conversation shortcut, the shortcut must be a 8583 * {@link ShortcutInfo.Builder#setLongLived(boolean)} dynamic or cached sharing shortcut. 8584 * 8585 * <p> 8586 * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. 8587 * Here's an example of how this may be used: 8588 * <pre class="prettyprint"> 8589 * 8590 * Person user = new Person.Builder().setIcon(userIcon).setName(userName).build(); 8591 * MessagingStyle style = new MessagingStyle(user) 8592 * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getPerson()) 8593 * .addMessage(messages[2].getText(), messages[2].getTime(), messages[2].getPerson()) 8594 * .setGroupConversation(hasMultiplePeople()); 8595 * 8596 * Notification noti = new Notification.Builder() 8597 * .setContentTitle("2 new messages with " + sender.toString()) 8598 * .setContentText(subject) 8599 * .setSmallIcon(R.drawable.new_message) 8600 * .setLargeIcon(aBitmap) 8601 * .setStyle(style) 8602 * .build(); 8603 * </pre> 8604 */ 8605 public static class MessagingStyle extends Style { 8606 8607 /** 8608 * The maximum number of messages that will be retained in the Notification itself (the 8609 * number displayed is up to the platform). 8610 */ 8611 public static final int MAXIMUM_RETAINED_MESSAGES = 25; 8612 8613 8614 /** @hide */ 8615 public static final int CONVERSATION_TYPE_LEGACY = 0; 8616 /** @hide */ 8617 public static final int CONVERSATION_TYPE_NORMAL = 1; 8618 /** @hide */ 8619 public static final int CONVERSATION_TYPE_IMPORTANT = 2; 8620 8621 /** @hide */ 8622 @IntDef(prefix = {"CONVERSATION_TYPE_"}, value = { 8623 CONVERSATION_TYPE_LEGACY, CONVERSATION_TYPE_NORMAL, CONVERSATION_TYPE_IMPORTANT 8624 }) 8625 @Retention(RetentionPolicy.SOURCE) 8626 public @interface ConversationType {} 8627 8628 @NonNull Person mUser; 8629 @Nullable CharSequence mConversationTitle; 8630 @Nullable Icon mShortcutIcon; 8631 List<Message> mMessages = new ArrayList<>(); 8632 List<Message> mHistoricMessages = new ArrayList<>(); 8633 boolean mIsGroupConversation; 8634 @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY; 8635 int mUnreadMessageCount; 8636 MessagingStyle()8637 MessagingStyle() { 8638 } 8639 8640 /** 8641 * @param userDisplayName Required - the name to be displayed for any replies sent by the 8642 * user before the posting app reposts the notification with those messages after they've 8643 * been actually sent and in previous messages sent by the user added in 8644 * {@link #addMessage(Notification.MessagingStyle.Message)} 8645 * 8646 * @deprecated use {@code MessagingStyle(Person)} 8647 */ MessagingStyle(@onNull CharSequence userDisplayName)8648 public MessagingStyle(@NonNull CharSequence userDisplayName) { 8649 this(new Person.Builder().setName(userDisplayName).build()); 8650 } 8651 8652 /** 8653 * @param user Required - The person displayed for any messages that are sent by the 8654 * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} 8655 * who don't have a Person associated with it will be displayed as if they were sent 8656 * by this user. The user also needs to have a valid name associated with it, which is 8657 * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}. 8658 */ MessagingStyle(@onNull Person user)8659 public MessagingStyle(@NonNull Person user) { 8660 mUser = user; 8661 } 8662 8663 /** 8664 * Validate that this style was properly composed. This is called at build time. 8665 * @hide 8666 */ 8667 @Override validate(Context context)8668 public void validate(Context context) { 8669 super.validate(context); 8670 if (context.getApplicationInfo().targetSdkVersion 8671 >= Build.VERSION_CODES.P && (mUser == null || mUser.getName() == null)) { 8672 throw new RuntimeException("User must be valid and have a name."); 8673 } 8674 } 8675 8676 /** 8677 * @return the text that should be displayed in the statusBar when heads upped. 8678 * If {@code null} is returned, the default implementation will be used. 8679 * 8680 * @hide 8681 */ 8682 @Override getHeadsUpStatusBarText()8683 public CharSequence getHeadsUpStatusBarText() { 8684 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 8685 ? super.mBigContentTitle 8686 : mConversationTitle; 8687 if (mConversationType == CONVERSATION_TYPE_LEGACY 8688 && !TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { 8689 return conversationTitle; 8690 } 8691 return null; 8692 } 8693 8694 /** 8695 * @return the user to be displayed for any replies sent by the user 8696 */ 8697 @NonNull getUser()8698 public Person getUser() { 8699 return mUser; 8700 } 8701 8702 /** 8703 * Returns the name to be displayed for any replies sent by the user 8704 * 8705 * @deprecated use {@link #getUser()} instead 8706 */ getUserDisplayName()8707 public CharSequence getUserDisplayName() { 8708 return mUser.getName(); 8709 } 8710 8711 /** 8712 * Sets the title to be displayed on this conversation. May be set to {@code null}. 8713 * 8714 * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored 8715 * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. 8716 * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing, 8717 * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title 8718 * instead. 8719 * 8720 * <p>This API's behavior was changed in SDK version {@link Build.VERSION_CODES#P}. If your 8721 * application's target version is less than {@link Build.VERSION_CODES#P}, setting a 8722 * conversation title to a non-null value will make {@link #isGroupConversation()} return 8723 * {@code true} and passing {@code null} will make it return {@code false}. In 8724 * {@link Build.VERSION_CODES#P} and beyond, use {@link #setGroupConversation(boolean)} 8725 * to set group conversation status. 8726 * 8727 * @param conversationTitle Title displayed for this conversation 8728 * @return this object for method chaining 8729 */ setConversationTitle(@ullable CharSequence conversationTitle)8730 public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) { 8731 mConversationTitle = conversationTitle; 8732 return this; 8733 } 8734 8735 /** 8736 * Return the title to be displayed on this conversation. May return {@code null}. 8737 */ 8738 @Nullable getConversationTitle()8739 public CharSequence getConversationTitle() { 8740 return mConversationTitle; 8741 } 8742 8743 /** 8744 * Sets the icon to be displayed on the conversation, derived from the shortcutId. 8745 * 8746 * @hide 8747 */ setShortcutIcon(@ullable Icon conversationIcon)8748 public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) { 8749 mShortcutIcon = conversationIcon; 8750 return this; 8751 } 8752 8753 /** 8754 * Return the icon to be displayed on this conversation, derived from the shortcutId. May 8755 * return {@code null}. 8756 * 8757 * @hide 8758 */ 8759 @Nullable getShortcutIcon()8760 public Icon getShortcutIcon() { 8761 return mShortcutIcon; 8762 } 8763 8764 /** 8765 * Sets the conversation type of this MessageStyle notification. 8766 * {@link #CONVERSATION_TYPE_LEGACY} will use the "older" layout from pre-R, 8767 * {@link #CONVERSATION_TYPE_NORMAL} will use the new "conversation" layout, and 8768 * {@link #CONVERSATION_TYPE_IMPORTANT} will add additional "important" treatments. 8769 * 8770 * @hide 8771 */ setConversationType(@onversationType int conversationType)8772 public MessagingStyle setConversationType(@ConversationType int conversationType) { 8773 mConversationType = conversationType; 8774 return this; 8775 } 8776 8777 /** @hide */ 8778 @ConversationType getConversationType()8779 public int getConversationType() { 8780 return mConversationType; 8781 } 8782 8783 /** @hide */ getUnreadMessageCount()8784 public int getUnreadMessageCount() { 8785 return mUnreadMessageCount; 8786 } 8787 8788 /** @hide */ setUnreadMessageCount(int unreadMessageCount)8789 public MessagingStyle setUnreadMessageCount(int unreadMessageCount) { 8790 mUnreadMessageCount = unreadMessageCount; 8791 return this; 8792 } 8793 8794 /** 8795 * Adds a message for display by this notification. Convenience call for a simple 8796 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 8797 * @param text A {@link CharSequence} to be displayed as the message content 8798 * @param timestamp Time in milliseconds at which the message arrived 8799 * @param sender A {@link CharSequence} to be used for displaying the name of the 8800 * sender. Should be <code>null</code> for messages by the current user, in which case 8801 * the platform will insert {@link #getUserDisplayName()}. 8802 * Should be unique amongst all individuals in the conversation, and should be 8803 * consistent during re-posts of the notification. 8804 * 8805 * @see Message#Message(CharSequence, long, CharSequence) 8806 * 8807 * @return this object for method chaining 8808 * 8809 * @deprecated use {@link #addMessage(CharSequence, long, Person)} 8810 */ addMessage(CharSequence text, long timestamp, CharSequence sender)8811 public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) { 8812 return addMessage(text, timestamp, 8813 sender == null ? null : new Person.Builder().setName(sender).build()); 8814 } 8815 8816 /** 8817 * Adds a message for display by this notification. Convenience call for a simple 8818 * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}. 8819 * @param text A {@link CharSequence} to be displayed as the message content 8820 * @param timestamp Time in milliseconds at which the message arrived 8821 * @param sender The {@link Person} who sent the message. 8822 * Should be <code>null</code> for messages by the current user, in which case 8823 * the platform will insert the user set in {@code MessagingStyle(Person)}. 8824 * 8825 * @see Message#Message(CharSequence, long, CharSequence) 8826 * 8827 * @return this object for method chaining 8828 */ addMessage(@onNull CharSequence text, long timestamp, @Nullable Person sender)8829 public MessagingStyle addMessage(@NonNull CharSequence text, long timestamp, 8830 @Nullable Person sender) { 8831 return addMessage(new Message(text, timestamp, sender)); 8832 } 8833 8834 /** 8835 * Adds a {@link Message} for display in this notification. 8836 * 8837 * <p>The messages should be added in chronologic order, i.e. the oldest first, 8838 * the newest last. 8839 * 8840 * <p>Multiple Messages in a row with the same timestamp and sender may be grouped as a 8841 * single message. This means an app should represent a message that has both an image and 8842 * text as two Message objects, one with the image (and fallback text), and the other with 8843 * the message text. For consistency, a text message (if any) should be provided after Uri 8844 * content. 8845 * 8846 * @param message The {@link Message} to be displayed 8847 * @return this object for method chaining 8848 */ addMessage(Message message)8849 public MessagingStyle addMessage(Message message) { 8850 mMessages.add(message); 8851 if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 8852 mMessages.remove(0); 8853 } 8854 return this; 8855 } 8856 8857 /** 8858 * Adds a {@link Message} for historic context in this notification. 8859 * 8860 * <p>Messages should be added as historic if they are not the main subject of the 8861 * notification but may give context to a conversation. The system may choose to present 8862 * them only when relevant, e.g. when replying to a message through a {@link RemoteInput}. 8863 * 8864 * <p>The messages should be added in chronologic order, i.e. the oldest first, 8865 * the newest last. 8866 * 8867 * @param message The historic {@link Message} to be added 8868 * @return this object for method chaining 8869 */ addHistoricMessage(Message message)8870 public MessagingStyle addHistoricMessage(Message message) { 8871 mHistoricMessages.add(message); 8872 if (mHistoricMessages.size() > MAXIMUM_RETAINED_MESSAGES) { 8873 mHistoricMessages.remove(0); 8874 } 8875 return this; 8876 } 8877 8878 /** 8879 * Gets the list of {@code Message} objects that represent the notification. 8880 */ getMessages()8881 public List<Message> getMessages() { 8882 return mMessages; 8883 } 8884 8885 /** 8886 * Gets the list of historic {@code Message}s in the notification. 8887 */ getHistoricMessages()8888 public List<Message> getHistoricMessages() { 8889 return mHistoricMessages; 8890 } 8891 8892 /** 8893 * Sets whether this conversation notification represents a group. If the app is targeting 8894 * Android P, this is required if the app wants to display the largeIcon set with 8895 * {@link Notification.Builder#setLargeIcon(Bitmap)}, otherwise it will be hidden. 8896 * 8897 * @param isGroupConversation {@code true} if the conversation represents a group, 8898 * {@code false} otherwise. 8899 * @return this object for method chaining 8900 */ setGroupConversation(boolean isGroupConversation)8901 public MessagingStyle setGroupConversation(boolean isGroupConversation) { 8902 mIsGroupConversation = isGroupConversation; 8903 return this; 8904 } 8905 8906 /** 8907 * Returns {@code true} if this notification represents a group conversation, otherwise 8908 * {@code false}. 8909 * 8910 * <p> If the application that generated this {@link MessagingStyle} targets an SDK version 8911 * less than {@link Build.VERSION_CODES#P}, this method becomes dependent on whether or 8912 * not the conversation title is set; returning {@code true} if the conversation title is 8913 * a non-null value, or {@code false} otherwise. From {@link Build.VERSION_CODES#P} forward, 8914 * this method returns what's set by {@link #setGroupConversation(boolean)} allowing for 8915 * named, non-group conversations. 8916 * 8917 * @see #setConversationTitle(CharSequence) 8918 */ isGroupConversation()8919 public boolean isGroupConversation() { 8920 // When target SDK version is < P, a non-null conversation title dictates if this is 8921 // as group conversation. 8922 if (mBuilder != null 8923 && mBuilder.mContext.getApplicationInfo().targetSdkVersion 8924 < Build.VERSION_CODES.P) { 8925 return mConversationTitle != null; 8926 } 8927 8928 return mIsGroupConversation; 8929 } 8930 8931 /** 8932 * @hide 8933 */ 8934 @Override addExtras(Bundle extras)8935 public void addExtras(Bundle extras) { 8936 super.addExtras(extras); 8937 addExtras(extras, false, 0); 8938 } 8939 8940 /** 8941 * @hide 8942 */ addExtras(Bundle extras, boolean ensureContrast, int backgroundColor)8943 public void addExtras(Bundle extras, boolean ensureContrast, int backgroundColor) { 8944 if (mUser != null) { 8945 // For legacy usages 8946 extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUser.getName()); 8947 extras.putParcelable(EXTRA_MESSAGING_PERSON, mUser); 8948 } 8949 if (mConversationTitle != null) { 8950 extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle); 8951 } 8952 if (!mMessages.isEmpty()) { 8953 extras.putParcelableArray(EXTRA_MESSAGES, 8954 getBundleArrayForMessages(mMessages, ensureContrast, backgroundColor)); 8955 } 8956 if (!mHistoricMessages.isEmpty()) { 8957 extras.putParcelableArray(EXTRA_HISTORIC_MESSAGES, getBundleArrayForMessages( 8958 mHistoricMessages, ensureContrast, backgroundColor)); 8959 } 8960 if (mShortcutIcon != null) { 8961 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon); 8962 } 8963 extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount); 8964 8965 fixTitleAndTextExtras(extras); 8966 extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation); 8967 } 8968 getBundleArrayForMessages(List<Message> messages, boolean ensureContrast, int backgroundColor)8969 private static Bundle[] getBundleArrayForMessages(List<Message> messages, 8970 boolean ensureContrast, int backgroundColor) { 8971 Bundle[] bundles = new Bundle[messages.size()]; 8972 final int N = messages.size(); 8973 for (int i = 0; i < N; i++) { 8974 final Message m = messages.get(i); 8975 if (ensureContrast) { 8976 m.ensureColorContrastOrStripStyling(backgroundColor); 8977 } 8978 bundles[i] = m.toBundle(); 8979 } 8980 return bundles; 8981 } 8982 fixTitleAndTextExtras(Bundle extras)8983 private void fixTitleAndTextExtras(Bundle extras) { 8984 Message m = findLatestIncomingMessage(); 8985 CharSequence text = (m == null) ? null : m.mText; 8986 CharSequence sender = m == null ? null 8987 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 8988 ? mUser.getName() : m.mSender.getName(); 8989 CharSequence title; 8990 if (!TextUtils.isEmpty(mConversationTitle)) { 8991 if (!TextUtils.isEmpty(sender)) { 8992 BidiFormatter bidi = BidiFormatter.getInstance(); 8993 title = mBuilder.mContext.getString( 8994 com.android.internal.R.string.notification_messaging_title_template, 8995 bidi.unicodeWrap(mConversationTitle), bidi.unicodeWrap(sender)); 8996 } else { 8997 title = mConversationTitle; 8998 } 8999 } else { 9000 title = sender; 9001 } 9002 if (Flags.cleanUpSpansAndNewLines()) { 9003 title = stripStyling(title); 9004 } 9005 if (title != null) { 9006 extras.putCharSequence(EXTRA_TITLE, title); 9007 } 9008 if (text != null) { 9009 extras.putCharSequence(EXTRA_TEXT, text); 9010 } 9011 } 9012 fixTitleAndTextForCompactMessaging(StandardTemplateParams p)9013 private void fixTitleAndTextForCompactMessaging(StandardTemplateParams p) { 9014 Message m = findLatestIncomingMessage(); 9015 final CharSequence text = (m == null) ? null : m.mText; 9016 CharSequence sender = m == null ? null 9017 : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) 9018 ? mUser.getName() : m.mSender.getName(); 9019 9020 CharSequence conversationTitle = mIsGroupConversation ? mConversationTitle : null; 9021 9022 // we want to have colon for possible title for conversation. 9023 final BidiFormatter bidi = BidiFormatter.getInstance(); 9024 if (sender != null) { 9025 sender = mBuilder.mContext.getString( 9026 com.android.internal.R.string.notification_messaging_title_template, 9027 bidi.unicodeWrap(sender), ""); 9028 } else if (conversationTitle != null) { 9029 conversationTitle = mBuilder.mContext.getString( 9030 com.android.internal.R.string.notification_messaging_title_template, 9031 bidi.unicodeWrap(conversationTitle), ""); 9032 } 9033 9034 if (Flags.cleanUpSpansAndNewLines()) { 9035 conversationTitle = stripStyling(conversationTitle); 9036 sender = stripStyling(sender); 9037 } 9038 9039 final CharSequence title; 9040 final boolean isConversationTitleAvailable = showConversationTitle() 9041 && conversationTitle != null; 9042 if (isConversationTitleAvailable) { 9043 title = conversationTitle; 9044 } else { 9045 title = sender; 9046 } 9047 9048 p.title(title); 9049 // when the conversation title is available, use headerTextSecondary for sender and 9050 // summaryText for text 9051 if (isConversationTitleAvailable) { 9052 p.headerTextSecondary(sender); 9053 p.summaryText(text); 9054 } else { 9055 // when it is not, use headerTextSecondary for text and don't use summaryText 9056 p.headerTextSecondary(text); 9057 p.summaryText(null); 9058 } 9059 } 9060 9061 /** (b/342370742) Developer settings to show conversation title. */ showConversationTitle()9062 private boolean showConversationTitle() { 9063 return SystemProperties.getBoolean( 9064 "persist.compact_heads_up_notification.show_conversation_title_for_group", 9065 false); 9066 } 9067 9068 /** 9069 * @hide 9070 */ 9071 @Override restoreFromExtras(Bundle extras)9072 protected void restoreFromExtras(Bundle extras) { 9073 super.restoreFromExtras(extras); 9074 9075 Person user = extras.getParcelable(EXTRA_MESSAGING_PERSON, Person.class); 9076 if (user == null) { 9077 CharSequence displayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME); 9078 mUser = new Person.Builder().setName(displayName).build(); 9079 } else { 9080 mUser = user; 9081 } 9082 mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE); 9083 Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class); 9084 mMessages = Message.getMessagesFromBundleArray(messages); 9085 Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES, 9086 Parcelable.class); 9087 mHistoricMessages = Message.getMessagesFromBundleArray(histMessages); 9088 mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION); 9089 mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT); 9090 mShortcutIcon = extras.getParcelable(EXTRA_CONVERSATION_ICON, Icon.class); 9091 } 9092 9093 /** 9094 * @hide 9095 */ 9096 @Override makeContentView(boolean increasedHeight)9097 public RemoteViews makeContentView(boolean increasedHeight) { 9098 // All messaging templates contain the actions 9099 ArrayList<Action> originalActions = mBuilder.mActions; 9100 try { 9101 mBuilder.mActions = new ArrayList<>(); 9102 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL); 9103 } finally { 9104 mBuilder.mActions = originalActions; 9105 } 9106 } 9107 9108 /** 9109 * @hide 9110 * Spans are ignored when comparing text for visual difference. 9111 */ 9112 @Override areNotificationsVisiblyDifferent(Style other)9113 public boolean areNotificationsVisiblyDifferent(Style other) { 9114 if (other == null || getClass() != other.getClass()) { 9115 return true; 9116 } 9117 MessagingStyle newS = (MessagingStyle) other; 9118 List<MessagingStyle.Message> oldMs = getMessages(); 9119 List<MessagingStyle.Message> newMs = newS.getMessages(); 9120 9121 if (oldMs == null || newMs == null) { 9122 newMs = new ArrayList<>(); 9123 } 9124 9125 int n = oldMs.size(); 9126 if (n != newMs.size()) { 9127 return true; 9128 } 9129 for (int i = 0; i < n; i++) { 9130 MessagingStyle.Message oldM = oldMs.get(i); 9131 MessagingStyle.Message newM = newMs.get(i); 9132 if (!Objects.equals( 9133 String.valueOf(oldM.getText()), 9134 String.valueOf(newM.getText()))) { 9135 return true; 9136 } 9137 if (!Objects.equals(oldM.getDataUri(), newM.getDataUri())) { 9138 return true; 9139 } 9140 String oldSender = String.valueOf(oldM.getSenderPerson() == null 9141 ? oldM.getSender() 9142 : oldM.getSenderPerson().getName()); 9143 String newSender = String.valueOf(newM.getSenderPerson() == null 9144 ? newM.getSender() 9145 : newM.getSenderPerson().getName()); 9146 if (!Objects.equals(oldSender, newSender)) { 9147 return true; 9148 } 9149 9150 String oldKey = oldM.getSenderPerson() == null 9151 ? null : oldM.getSenderPerson().getKey(); 9152 String newKey = newM.getSenderPerson() == null 9153 ? null : newM.getSenderPerson().getKey(); 9154 if (!Objects.equals(oldKey, newKey)) { 9155 return true; 9156 } 9157 // Other fields (like timestamp) intentionally excluded 9158 } 9159 return false; 9160 } 9161 findLatestIncomingMessage()9162 private Message findLatestIncomingMessage() { 9163 return findLatestIncomingMessage(mMessages); 9164 } 9165 9166 /** 9167 * @hide 9168 */ 9169 @Nullable findLatestIncomingMessage( List<Message> messages)9170 public static Message findLatestIncomingMessage( 9171 List<Message> messages) { 9172 for (int i = messages.size() - 1; i >= 0; i--) { 9173 Message m = messages.get(i); 9174 // Incoming messages have a non-empty sender. 9175 if (m.mSender != null && !TextUtils.isEmpty(m.mSender.getName())) { 9176 return m; 9177 } 9178 } 9179 if (!messages.isEmpty()) { 9180 // No incoming messages, fall back to outgoing message 9181 return messages.get(messages.size() - 1); 9182 } 9183 return null; 9184 } 9185 9186 /** 9187 * @hide 9188 */ 9189 @Override makeBigContentView()9190 public RemoteViews makeBigContentView() { 9191 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG); 9192 } 9193 9194 /** 9195 * Create a messaging layout. 9196 * 9197 * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG, 9198 * VIEW_TYPE_HEADS_UP 9199 * @return the created remoteView. 9200 */ 9201 @NonNull makeMessagingView(int viewType)9202 private RemoteViews makeMessagingView(int viewType) { 9203 boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG; 9204 boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL; 9205 boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 9206 boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT; 9207 boolean isHeaderless = !isConversationLayout && isCollapsed; 9208 9209 //TODO (b/217799515): ensure mConversationTitle always returns the correct 9210 // conversationTitle, probably set mConversationTitle = conversationTitle after this 9211 CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) 9212 ? super.mBigContentTitle 9213 : mConversationTitle; 9214 boolean atLeastP = mBuilder.mContext.getApplicationInfo().targetSdkVersion 9215 >= Build.VERSION_CODES.P; 9216 boolean isOneToOne; 9217 CharSequence nameReplacement = null; 9218 if (!atLeastP) { 9219 isOneToOne = TextUtils.isEmpty(conversationTitle); 9220 if (hasOnlyWhiteSpaceSenders()) { 9221 isOneToOne = true; 9222 nameReplacement = conversationTitle; 9223 conversationTitle = null; 9224 } 9225 } else { 9226 isOneToOne = !isGroupConversation(); 9227 } 9228 if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) { 9229 conversationTitle = getOtherPersonName(); 9230 } 9231 9232 Icon largeIcon = mBuilder.mN.mLargeIcon; 9233 TemplateBindResult bindResult = new TemplateBindResult(); 9234 StandardTemplateParams p = mBuilder.mParams.reset() 9235 .viewType(viewType) 9236 .highlightExpander(isConversationLayout) 9237 .hideProgress(true) 9238 .title(isHeaderless ? conversationTitle : null) 9239 .text(null) 9240 .hideLeftIcon(isOneToOne) 9241 .hideRightIcon(hideRightIcons || isOneToOne) 9242 .headerTextSecondary(isHeaderless ? null : conversationTitle); 9243 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions( 9244 isConversationLayout 9245 ? mBuilder.getConversationLayoutResource() 9246 : isCollapsed 9247 ? mBuilder.getMessagingLayoutResource() 9248 : mBuilder.getBigMessagingLayoutResource(), 9249 p, 9250 bindResult); 9251 if (isConversationLayout) { 9252 mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p); 9253 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 9254 } 9255 9256 addExtras(mBuilder.mN.extras, true, mBuilder.getBackgroundColor(p)); 9257 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 9258 mBuilder.getSmallIconColor(p)); 9259 contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor", 9260 mBuilder.getPrimaryTextColor(p)); 9261 contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor", 9262 mBuilder.getSecondaryTextColor(p)); 9263 contentView.setInt(R.id.status_bar_latest_event_content, 9264 "setNotificationBackgroundColor", 9265 mBuilder.getBackgroundColor(p)); 9266 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed", 9267 isCollapsed); 9268 contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement", 9269 mBuilder.mN.mLargeIcon); 9270 contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement", 9271 nameReplacement); 9272 contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne", 9273 isOneToOne); 9274 contentView.setCharSequence(R.id.status_bar_latest_event_content, 9275 "setConversationTitle", conversationTitle); 9276 if (isConversationLayout) { 9277 contentView.setIcon(R.id.status_bar_latest_event_content, 9278 "setShortcutIcon", mShortcutIcon); 9279 contentView.setBoolean(R.id.status_bar_latest_event_content, 9280 "setIsImportantConversation", isImportantConversation); 9281 } 9282 if (isHeaderless) { 9283 // Collapsed legacy messaging style has a 1-line limit. 9284 contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1); 9285 } 9286 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 9287 largeIcon); 9288 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 9289 mBuilder.mN.extras); 9290 return contentView; 9291 } 9292 getKey(Person person)9293 private CharSequence getKey(Person person) { 9294 return person == null ? null 9295 : person.getKey() == null ? person.getName() : person.getKey(); 9296 } 9297 getOtherPersonName()9298 private CharSequence getOtherPersonName() { 9299 CharSequence userKey = getKey(mUser); 9300 for (int i = mMessages.size() - 1; i >= 0; i--) { 9301 Person sender = mMessages.get(i).getSenderPerson(); 9302 if (sender != null && !TextUtils.equals(userKey, getKey(sender))) { 9303 return sender.getName(); 9304 } 9305 } 9306 return null; 9307 } 9308 hasOnlyWhiteSpaceSenders()9309 private boolean hasOnlyWhiteSpaceSenders() { 9310 for (int i = 0; i < mMessages.size(); i++) { 9311 Message m = mMessages.get(i); 9312 Person sender = m.getSenderPerson(); 9313 if (sender != null && !isWhiteSpace(sender.getName())) { 9314 return false; 9315 } 9316 } 9317 return true; 9318 } 9319 isWhiteSpace(CharSequence sender)9320 private boolean isWhiteSpace(CharSequence sender) { 9321 if (TextUtils.isEmpty(sender)) { 9322 return true; 9323 } 9324 if (sender.toString().matches("^\\s*$")) { 9325 return true; 9326 } 9327 // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround 9328 // For the presentation that we had. 9329 for (int i = 0; i < sender.length(); i++) { 9330 char c = sender.charAt(i); 9331 if (c != '\u200B') { 9332 return false; 9333 } 9334 } 9335 return true; 9336 } 9337 9338 /** 9339 * @hide 9340 */ 9341 @Override makeHeadsUpContentView(boolean increasedHeight)9342 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 9343 return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 9344 } 9345 9346 /** 9347 * @hide 9348 */ 9349 @Nullable 9350 @Override makeCompactHeadsUpContentView()9351 public RemoteViews makeCompactHeadsUpContentView() { 9352 final boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; 9353 Icon conversationIcon = null; 9354 Notification.Action remoteInputAction = null; 9355 if (isConversationLayout) { 9356 9357 conversationIcon = mShortcutIcon; 9358 9359 // conversation icon is m 9360 // Extract the conversation icon for one to one conversations from 9361 // the latest incoming message since 9362 // fixTitleAndTextExtras also uses it as data source for title and text 9363 if (conversationIcon == null && !mIsGroupConversation) { 9364 final Message message = findLatestIncomingMessage(); 9365 if (message != null) { 9366 final Person sender = message.mSender; 9367 if (sender != null) { 9368 conversationIcon = sender.getIcon(); 9369 } 9370 } 9371 } 9372 9373 if (Flags.compactHeadsUpNotificationReply()) { 9374 // Get the first non-contextual inline reply action. 9375 final List<Notification.Action> nonContextualActions = 9376 mBuilder.getNonContextualActions(); 9377 for (int i = 0; i < nonContextualActions.size(); i++) { 9378 final Notification.Action action = nonContextualActions.get(i); 9379 if (mBuilder.hasValidRemoteInput(action)) { 9380 remoteInputAction = action; 9381 break; 9382 } 9383 } 9384 } 9385 } 9386 9387 final StandardTemplateParams p = mBuilder.mParams.reset() 9388 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 9389 .highlightExpander(isConversationLayout) 9390 .fillTextsFrom(mBuilder) 9391 .hideTime(true); 9392 9393 fixTitleAndTextForCompactMessaging(p); 9394 TemplateBindResult bindResult = new TemplateBindResult(); 9395 9396 RemoteViews contentView = mBuilder.applyStandardTemplate( 9397 mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult); 9398 contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); 9399 contentView.setViewVisibility(R.id.header_text_divider, View.GONE); 9400 if (conversationIcon != null) { 9401 contentView.setViewVisibility(R.id.icon, View.GONE); 9402 contentView.setViewVisibility(R.id.conversation_face_pile, View.GONE); 9403 contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE); 9404 contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true); 9405 contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon); 9406 } else if (mIsGroupConversation) { 9407 contentView.setViewVisibility(R.id.icon, View.GONE); 9408 contentView.setViewVisibility(R.id.conversation_icon, View.GONE); 9409 contentView.setInt(R.id.status_bar_latest_event_content, 9410 "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); 9411 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 9412 mBuilder.getSmallIconColor(p)); 9413 contentView.setBundle(R.id.status_bar_latest_event_content, "setGroupFacePile", 9414 mBuilder.mN.extras); 9415 } 9416 9417 if (remoteInputAction != null) { 9418 contentView.setViewVisibility(R.id.reply_action_container, View.VISIBLE); 9419 9420 final RemoteViews inlineReplyButton = 9421 mBuilder.generateActionButton(remoteInputAction, false, p); 9422 // Clear the drawable 9423 inlineReplyButton.setInt(R.id.action0, "setBackgroundResource", 0); 9424 inlineReplyButton.setTextViewText(R.id.action0, 9425 mBuilder.mContext.getString(R.string.notification_compact_heads_up_reply)); 9426 contentView.addView(R.id.reply_action_container, inlineReplyButton); 9427 } else { 9428 contentView.setViewVisibility(R.id.reply_action_container, View.GONE); 9429 } 9430 return contentView; 9431 } 9432 9433 9434 /** 9435 * @hide 9436 */ 9437 @Override reduceImageSizes(Context context)9438 public void reduceImageSizes(Context context) { 9439 super.reduceImageSizes(context); 9440 Resources resources = context.getResources(); 9441 boolean isLowRam = ActivityManager.isLowRamDeviceStatic(); 9442 if (mShortcutIcon != null) { 9443 int maxSize = resources.getDimensionPixelSize( 9444 isLowRam ? R.dimen.notification_small_icon_size_low_ram 9445 : R.dimen.notification_small_icon_size); 9446 mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize); 9447 } 9448 9449 int maxAvatarSize = resources.getDimensionPixelSize( 9450 isLowRam ? R.dimen.notification_person_icon_max_size_low_ram 9451 : R.dimen.notification_person_icon_max_size); 9452 if (mUser != null && mUser.getIcon() != null) { 9453 mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize); 9454 } 9455 9456 reduceMessagesIconSizes(mMessages, maxAvatarSize); 9457 reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize); 9458 } 9459 9460 /** 9461 * @hide 9462 */ reduceMessagesIconSizes(@ullable List<Message> messages, int maxSize)9463 private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) { 9464 if (messages == null) { 9465 return; 9466 } 9467 9468 for (Message message : messages) { 9469 Person sender = message.mSender; 9470 if (sender != null) { 9471 Icon icon = sender.getIcon(); 9472 if (icon != null) { 9473 icon.scaleDownIfNecessary(maxSize, maxSize); 9474 } 9475 } 9476 } 9477 } 9478 9479 /* 9480 * An object representing a simple message or piece of media within a mixed-media message. 9481 * 9482 * This object can only represent text or a single binary piece of media. For apps which 9483 * support mixed-media messages (e.g. text + image), multiple Messages should be used, one 9484 * to represent each piece of the message, and they should all be given the same timestamp. 9485 * For consistency, a text message should be added last of all Messages with the same 9486 * timestamp. 9487 */ 9488 public static final class Message { 9489 /** @hide */ 9490 public static final String KEY_TEXT = "text"; 9491 static final String KEY_TIMESTAMP = "time"; 9492 static final String KEY_SENDER = "sender"; 9493 static final String KEY_SENDER_PERSON = "sender_person"; 9494 static final String KEY_DATA_MIME_TYPE = "type"; 9495 static final String KEY_DATA_URI= "uri"; 9496 static final String KEY_EXTRAS_BUNDLE = "extras"; 9497 static final String KEY_REMOTE_INPUT_HISTORY = "remote_input_history"; 9498 9499 private CharSequence mText; 9500 private final long mTimestamp; 9501 @Nullable 9502 private final Person mSender; 9503 /** True if this message was generated from the extra 9504 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS} 9505 */ 9506 private final boolean mRemoteInputHistory; 9507 9508 private Bundle mExtras = new Bundle(); 9509 private String mDataMimeType; 9510 private Uri mDataUri; 9511 9512 /** 9513 * Constructor 9514 * @param text A {@link CharSequence} to be displayed as the message content 9515 * @param timestamp Time at which the message arrived 9516 * @param sender A {@link CharSequence} to be used for displaying the name of the 9517 * sender. Should be <code>null</code> for messages by the current user, in which case 9518 * the platform will insert {@link MessagingStyle#getUserDisplayName()}. 9519 * Should be unique amongst all individuals in the conversation, and should be 9520 * consistent during re-posts of the notification. 9521 * 9522 * @deprecated use {@code Message(CharSequence, long, Person)} 9523 */ Message(CharSequence text, long timestamp, CharSequence sender)9524 public Message(CharSequence text, long timestamp, CharSequence sender){ 9525 this(text, timestamp, sender == null ? null 9526 : new Person.Builder().setName(sender).build()); 9527 } 9528 9529 /** 9530 * Constructor 9531 * @param text A {@link CharSequence} to be displayed as the message content 9532 * @param timestamp Time at which the message arrived 9533 * @param sender The {@link Person} who sent the message. 9534 * Should be <code>null</code> for messages by the current user, in which case 9535 * the platform will insert the user set in {@code MessagingStyle(Person)}. 9536 * <p> 9537 * The person provided should contain an Icon, set with 9538 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 9539 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 9540 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 9541 * to differentiate between the different users. 9542 * </p> 9543 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender)9544 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender) { 9545 this(text, timestamp, sender, false /* remoteHistory */); 9546 } 9547 9548 /** 9549 * Constructor 9550 * @param text A {@link CharSequence} to be displayed as the message content 9551 * @param timestamp Time at which the message arrived 9552 * @param sender The {@link Person} who sent the message. 9553 * Should be <code>null</code> for messages by the current user, in which case 9554 * the platform will insert the user set in {@code MessagingStyle(Person)}. 9555 * @param remoteInputHistory True if the messages was generated from the extra 9556 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 9557 * <p> 9558 * The person provided should contain an Icon, set with 9559 * {@link Person.Builder#setIcon(Icon)} and also have a name provided 9560 * with {@link Person.Builder#setName(CharSequence)}. If multiple users have the same 9561 * name, consider providing a key with {@link Person.Builder#setKey(String)} in order 9562 * to differentiate between the different users. 9563 * </p> 9564 * @hide 9565 */ Message(@onNull CharSequence text, long timestamp, @Nullable Person sender, boolean remoteInputHistory)9566 public Message(@NonNull CharSequence text, long timestamp, @Nullable Person sender, 9567 boolean remoteInputHistory) { 9568 mText = safeCharSequence(text); 9569 mTimestamp = timestamp; 9570 mSender = sender; 9571 mRemoteInputHistory = remoteInputHistory; 9572 } 9573 9574 /** 9575 * Sets a binary blob of data and an associated MIME type for a message. In the case 9576 * where the platform or the UI state doesn't support the MIME type, the original text 9577 * provided in the constructor will be used. When this data can be presented to the 9578 * user, the original text will only be used as accessibility text. 9579 * @param dataMimeType The MIME type of the content. See 9580 * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)} for a list of 9581 * supported image MIME types. 9582 * @param dataUri The uri containing the content whose type is given by the MIME type. 9583 * <p class="note"> 9584 * Notification Listeners including the System UI need permission to access the 9585 * data the Uri points to. The recommended ways to do this are: 9586 * <ol> 9587 * <li>Store the data in your own ContentProvider, making sure that other apps have 9588 * the correct permission to access your provider. The preferred mechanism for 9589 * providing access is to use per-URI permissions which are temporary and only 9590 * grant access to the receiving application. An easy way to create a 9591 * ContentProvider like this is to use the FileProvider helper class.</li> 9592 * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio 9593 * and image MIME types, however beginning with Android 3.0 (API level 11) it can 9594 * also store non-media types (see MediaStore.Files for more info). Files can be 9595 * inserted into the MediaStore using scanFile() after which a content:// style 9596 * Uri suitable for sharing is passed to the provided onScanCompleted() callback. 9597 * Note that once added to the system MediaStore the content is accessible to any 9598 * app on the device.</li> 9599 * </ol> 9600 * @return this object for method chaining 9601 */ setData(String dataMimeType, Uri dataUri)9602 public Message setData(String dataMimeType, Uri dataUri) { 9603 mDataMimeType = dataMimeType; 9604 mDataUri = dataUri; 9605 return this; 9606 } 9607 9608 /** 9609 * Strip styling or updates TextAppearance spans in message text. 9610 * @hide 9611 */ ensureColorContrastOrStripStyling(int backgroundColor)9612 public void ensureColorContrastOrStripStyling(int backgroundColor) { 9613 if (Flags.cleanUpSpansAndNewLines()) { 9614 mText = stripNonStyleSpans(mText); 9615 } else { 9616 ensureColorContrast(backgroundColor); 9617 } 9618 } 9619 stripNonStyleSpans(CharSequence text)9620 private CharSequence stripNonStyleSpans(CharSequence text) { 9621 9622 if (text instanceof Spanned) { 9623 Spanned ss = (Spanned) text; 9624 Object[] spans = ss.getSpans(0, ss.length(), Object.class); 9625 SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString()); 9626 for (Object span : spans) { 9627 final Object resultSpan; 9628 if (span instanceof StyleSpan 9629 || span instanceof StrikethroughSpan 9630 || span instanceof UnderlineSpan) { 9631 resultSpan = span; 9632 } else if (span instanceof TextAppearanceSpan) { 9633 final TextAppearanceSpan originalSpan = (TextAppearanceSpan) span; 9634 resultSpan = new TextAppearanceSpan( 9635 null, 9636 originalSpan.getTextStyle(), 9637 -1, 9638 null, 9639 null); 9640 } else { 9641 continue; 9642 } 9643 builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span), 9644 ss.getSpanFlags(span)); 9645 } 9646 return builder; 9647 } 9648 return text; 9649 } 9650 9651 /** 9652 * Updates TextAppearance spans in the message text so it has sufficient contrast 9653 * against its background. 9654 * @hide 9655 */ ensureColorContrast(int backgroundColor)9656 public void ensureColorContrast(int backgroundColor) { 9657 mText = ContrastColorUtil.ensureColorSpanContrast(mText, backgroundColor); 9658 } 9659 9660 /** 9661 * Get the text to be used for this message, or the fallback text if a type and content 9662 * Uri have been set 9663 */ getText()9664 public CharSequence getText() { 9665 return mText; 9666 } 9667 9668 /** 9669 * Get the time at which this message arrived 9670 */ getTimestamp()9671 public long getTimestamp() { 9672 return mTimestamp; 9673 } 9674 9675 /** 9676 * Get the extras Bundle for this message. 9677 */ getExtras()9678 public Bundle getExtras() { 9679 return mExtras; 9680 } 9681 9682 /** 9683 * Get the text used to display the contact's name in the messaging experience 9684 * 9685 * @deprecated use {@link #getSenderPerson()} 9686 */ getSender()9687 public CharSequence getSender() { 9688 return mSender == null ? null : mSender.getName(); 9689 } 9690 9691 /** 9692 * Get the sender associated with this message. 9693 */ 9694 @Nullable getSenderPerson()9695 public Person getSenderPerson() { 9696 return mSender; 9697 } 9698 9699 /** 9700 * Get the MIME type of the data pointed to by the Uri 9701 */ getDataMimeType()9702 public String getDataMimeType() { 9703 return mDataMimeType; 9704 } 9705 9706 /** 9707 * Get the Uri pointing to the content of the message. Can be null, in which case 9708 * {@see #getText()} is used. 9709 */ getDataUri()9710 public Uri getDataUri() { 9711 return mDataUri; 9712 } 9713 9714 /** 9715 * @return True if the message was generated from 9716 * {@link Notification#EXTRA_REMOTE_INPUT_HISTORY_ITEMS}. 9717 * @hide 9718 */ isRemoteInputHistory()9719 public boolean isRemoteInputHistory() { 9720 return mRemoteInputHistory; 9721 } 9722 9723 /** 9724 * Converts the message into a {@link Bundle}. To extract the message back, 9725 * check {@link #getMessageFromBundle()} 9726 * @hide 9727 */ 9728 @NonNull toBundle()9729 public Bundle toBundle() { 9730 Bundle bundle = new Bundle(); 9731 if (mText != null) { 9732 bundle.putCharSequence(KEY_TEXT, mText); 9733 } 9734 bundle.putLong(KEY_TIMESTAMP, mTimestamp); 9735 if (mSender != null) { 9736 // Legacy listeners need this 9737 bundle.putCharSequence(KEY_SENDER, safeCharSequence(mSender.getName())); 9738 bundle.putParcelable(KEY_SENDER_PERSON, mSender); 9739 } 9740 if (mDataMimeType != null) { 9741 bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType); 9742 } 9743 if (mDataUri != null) { 9744 bundle.putParcelable(KEY_DATA_URI, mDataUri); 9745 } 9746 if (mExtras != null) { 9747 bundle.putBundle(KEY_EXTRAS_BUNDLE, mExtras); 9748 } 9749 if (mRemoteInputHistory) { 9750 bundle.putBoolean(KEY_REMOTE_INPUT_HISTORY, mRemoteInputHistory); 9751 } 9752 return bundle; 9753 } 9754 9755 /** 9756 * See {@link Notification#visitUris(Consumer)}. 9757 * 9758 * @hide 9759 */ visitUris(@onNull Consumer<Uri> visitor)9760 public void visitUris(@NonNull Consumer<Uri> visitor) { 9761 visitor.accept(getDataUri()); 9762 if (mSender != null) { 9763 mSender.visitUris(visitor); 9764 } 9765 } 9766 9767 /** 9768 * Returns a list of messages read from the given bundle list, e.g. 9769 * {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}. 9770 */ 9771 @NonNull getMessagesFromBundleArray(@ullable Parcelable[] bundles)9772 public static List<Message> getMessagesFromBundleArray(@Nullable Parcelable[] bundles) { 9773 if (bundles == null) { 9774 return new ArrayList<>(); 9775 } 9776 List<Message> messages = new ArrayList<>(bundles.length); 9777 for (int i = 0; i < bundles.length; i++) { 9778 if (bundles[i] instanceof Bundle) { 9779 Message message = getMessageFromBundle((Bundle)bundles[i]); 9780 if (message != null) { 9781 messages.add(message); 9782 } 9783 } 9784 } 9785 return messages; 9786 } 9787 9788 /** 9789 * Returns the message that is stored in the bundle (e.g. one of the values in the lists 9790 * in {@link #EXTRA_MESSAGES} or {@link #EXTRA_HISTORIC_MESSAGES}) or null if the 9791 * message couldn't be resolved. 9792 * @hide 9793 */ 9794 @Nullable getMessageFromBundle(@onNull Bundle bundle)9795 public static Message getMessageFromBundle(@NonNull Bundle bundle) { 9796 try { 9797 if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) { 9798 return null; 9799 } else { 9800 9801 Person senderPerson = bundle.getParcelable(KEY_SENDER_PERSON, Person.class); 9802 if (senderPerson == null) { 9803 // Legacy apps that use compat don't actually provide the sender objects 9804 // We need to fix the compat version to provide people / use 9805 // the native api instead 9806 CharSequence senderName = bundle.getCharSequence(KEY_SENDER); 9807 if (senderName != null) { 9808 senderPerson = new Person.Builder().setName(senderName).build(); 9809 } 9810 } 9811 Message message = new Message(bundle.getCharSequence(KEY_TEXT), 9812 bundle.getLong(KEY_TIMESTAMP), 9813 senderPerson, 9814 bundle.getBoolean(KEY_REMOTE_INPUT_HISTORY, false)); 9815 if (bundle.containsKey(KEY_DATA_MIME_TYPE) && 9816 bundle.containsKey(KEY_DATA_URI)) { 9817 message.setData(bundle.getString(KEY_DATA_MIME_TYPE), 9818 bundle.getParcelable(KEY_DATA_URI, Uri.class)); 9819 } 9820 if (bundle.containsKey(KEY_EXTRAS_BUNDLE)) { 9821 message.getExtras().putAll(bundle.getBundle(KEY_EXTRAS_BUNDLE)); 9822 } 9823 return message; 9824 } 9825 } catch (ClassCastException e) { 9826 return null; 9827 } 9828 } 9829 } 9830 } 9831 9832 /** 9833 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 9834 * 9835 * Here's how you'd set the <code>InboxStyle</code> on a notification: 9836 * <pre class="prettyprint"> 9837 * Notification notif = new Notification.Builder(mContext) 9838 * .setContentTitle("5 New mails from " + sender.toString()) 9839 * .setContentText(subject) 9840 * .setSmallIcon(R.drawable.new_mail) 9841 * .setLargeIcon(aBitmap) 9842 * .setStyle(new Notification.InboxStyle() 9843 * .addLine(str1) 9844 * .addLine(str2) 9845 * .setContentTitle("") 9846 * .setSummaryText("+3 more")) 9847 * .build(); 9848 * </pre> 9849 * 9850 * @see Notification#bigContentView 9851 */ 9852 public static class InboxStyle extends Style { 9853 9854 /** 9855 * The number of lines of remote input history allowed until we start reducing lines. 9856 */ 9857 private static final int NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION = 1; 9858 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 9859 InboxStyle()9860 public InboxStyle() { 9861 } 9862 9863 /** 9864 * @deprecated use {@code InboxStyle()}. 9865 */ 9866 @Deprecated InboxStyle(Builder builder)9867 public InboxStyle(Builder builder) { 9868 setBuilder(builder); 9869 } 9870 9871 /** 9872 * Overrides ContentTitle in the big form of the template. 9873 * This defaults to the value passed to setContentTitle(). 9874 */ setBigContentTitle(CharSequence title)9875 public InboxStyle setBigContentTitle(CharSequence title) { 9876 internalSetBigContentTitle(safeCharSequence(title)); 9877 return this; 9878 } 9879 9880 /** 9881 * Set the first line of text after the detail section in the big form of the template. 9882 */ setSummaryText(CharSequence cs)9883 public InboxStyle setSummaryText(CharSequence cs) { 9884 internalSetSummaryText(safeCharSequence(cs)); 9885 return this; 9886 } 9887 9888 /** 9889 * Append a line to the digest section of the Inbox notification. 9890 */ addLine(CharSequence cs)9891 public InboxStyle addLine(CharSequence cs) { 9892 mTexts.add(safeCharSequence(cs)); 9893 return this; 9894 } 9895 9896 /** 9897 * @hide 9898 */ getLines()9899 public ArrayList<CharSequence> getLines() { 9900 return mTexts; 9901 } 9902 9903 /** 9904 * @hide 9905 */ addExtras(Bundle extras)9906 public void addExtras(Bundle extras) { 9907 super.addExtras(extras); 9908 9909 CharSequence[] a = new CharSequence[mTexts.size()]; 9910 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 9911 } 9912 9913 /** 9914 * @hide 9915 */ 9916 @Override restoreFromExtras(Bundle extras)9917 protected void restoreFromExtras(Bundle extras) { 9918 super.restoreFromExtras(extras); 9919 9920 mTexts.clear(); 9921 if (extras.containsKey(EXTRA_TEXT_LINES)) { 9922 Collections.addAll(mTexts, extras.getCharSequenceArray(EXTRA_TEXT_LINES)); 9923 } 9924 } 9925 9926 /** 9927 * @hide 9928 */ makeBigContentView()9929 public RemoteViews makeBigContentView() { 9930 StandardTemplateParams p = mBuilder.mParams.reset() 9931 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 9932 .fillTextsFrom(mBuilder).text(null); 9933 TemplateBindResult result = new TemplateBindResult(); 9934 RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result); 9935 9936 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 9937 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 9938 9939 // Make sure all rows are gone in case we reuse a view. 9940 for (int rowId : rowIds) { 9941 contentView.setViewVisibility(rowId, View.GONE); 9942 } 9943 9944 int i=0; 9945 int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 9946 R.dimen.notification_inbox_item_top_padding); 9947 boolean first = true; 9948 int onlyViewId = 0; 9949 int maxRows = rowIds.length; 9950 if (mBuilder.mActions.size() > 0) { 9951 maxRows--; 9952 } 9953 RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle( 9954 mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, 9955 RemoteInputHistoryItem.class); 9956 if (remoteInputHistory != null 9957 && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) { 9958 // Let's remove some messages to make room for the remote input history. 9959 // 1 is always able to fit, but let's remove them if they are 2 or 3 9960 int numRemoteInputs = Math.min(remoteInputHistory.length, 9961 MAX_REMOTE_INPUT_HISTORY_LINES); 9962 int totalNumRows = mTexts.size() + numRemoteInputs 9963 - NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION; 9964 if (totalNumRows > maxRows) { 9965 int overflow = totalNumRows - maxRows; 9966 if (mTexts.size() > maxRows) { 9967 // Heuristic: if the Texts don't fit anyway, we'll rather drop the last 9968 // few messages, even with the remote input 9969 maxRows -= overflow; 9970 } else { 9971 // otherwise we drop the first messages 9972 i = overflow; 9973 } 9974 } 9975 } 9976 while (i < mTexts.size() && i < maxRows) { 9977 CharSequence str = mTexts.get(i); 9978 if (!TextUtils.isEmpty(str)) { 9979 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 9980 contentView.setTextViewText(rowIds[i], 9981 mBuilder.ensureColorSpanContrastOrStripStyling( 9982 mBuilder.processLegacyText(str), p)); 9983 mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p); 9984 contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0); 9985 if (first) { 9986 onlyViewId = rowIds[i]; 9987 } else { 9988 onlyViewId = 0; 9989 } 9990 first = false; 9991 } 9992 i++; 9993 } 9994 if (onlyViewId != 0) { 9995 // We only have 1 entry, lets make it look like the normal Text of a Bigtext 9996 topPadding = mBuilder.mContext.getResources().getDimensionPixelSize( 9997 R.dimen.notification_text_margin_top); 9998 contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0); 9999 } 10000 10001 return contentView; 10002 } 10003 10004 /** 10005 * @hide 10006 */ 10007 @Override areNotificationsVisiblyDifferent(Style other)10008 public boolean areNotificationsVisiblyDifferent(Style other) { 10009 if (other == null || getClass() != other.getClass()) { 10010 return true; 10011 } 10012 InboxStyle newS = (InboxStyle) other; 10013 10014 final ArrayList<CharSequence> myLines = getLines(); 10015 final ArrayList<CharSequence> newLines = newS.getLines(); 10016 final int n = myLines.size(); 10017 if (n != newLines.size()) { 10018 return true; 10019 } 10020 10021 for (int i = 0; i < n; i++) { 10022 if (!Objects.equals( 10023 String.valueOf(myLines.get(i)), 10024 String.valueOf(newLines.get(i)))) { 10025 return true; 10026 } 10027 } 10028 return false; 10029 } 10030 } 10031 10032 /** 10033 * Notification style for media playback notifications. 10034 * 10035 * In the expanded form, {@link Notification#bigContentView}, up to 5 10036 * {@link Notification.Action}s specified with 10037 * {@link Notification.Builder#addAction(Action) addAction} will be 10038 * shown as icon-only pushbuttons, suitable for transport controls. The Bitmap given to 10039 * {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap) setLargeIcon()} will be 10040 * treated as album artwork. 10041 * <p> 10042 * Unlike the other styles provided here, MediaStyle can also modify the standard-size 10043 * {@link Notification#contentView}; by providing action indices to 10044 * {@link #setShowActionsInCompactView(int...)} you can promote up to 3 actions to be displayed 10045 * in the standard view alongside the usual content. 10046 * <p> 10047 * Notifications created with MediaStyle will have their category set to 10048 * {@link Notification#CATEGORY_TRANSPORT CATEGORY_TRANSPORT} unless you set a different 10049 * category using {@link Notification.Builder#setCategory(String) setCategory()}. 10050 * <p> 10051 * Finally, if you attach a {@link android.media.session.MediaSession.Token} using 10052 * {@link android.app.Notification.MediaStyle#setMediaSession(MediaSession.Token)}, 10053 * the System UI can identify this as a notification representing an active media session 10054 * and respond accordingly (by showing album artwork in the lockscreen, for example). 10055 * 10056 * <p> 10057 * Starting at {@link android.os.Build.VERSION_CODES#O Android O} any notification that has a 10058 * media session attached with {@link #setMediaSession(MediaSession.Token)} will be colorized. 10059 * You can opt-out of this behavior by using {@link Notification.Builder#setColorized(boolean)}. 10060 * <p> 10061 * 10062 * <p> 10063 * Starting at {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM Android V} the 10064 * {@link Notification#FLAG_NO_CLEAR NO_CLEAR flag} will be set for valid MediaStyle 10065 * notifications. 10066 * <p> 10067 * 10068 * To use this style with your Notification, feed it to 10069 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 10070 * <pre class="prettyprint"> 10071 * Notification noti = new Notification.Builder() 10072 * .setSmallIcon(R.drawable.ic_stat_player) 10073 * .setContentTitle("Track title") 10074 * .setContentText("Artist - Album") 10075 * .setLargeIcon(albumArtBitmap)) 10076 * .setStyle(<b>new Notification.MediaStyle()</b> 10077 * .setMediaSession(mySession)) 10078 * .build(); 10079 * </pre> 10080 * 10081 * @see Notification#bigContentView 10082 * @see Notification.Builder#setColorized(boolean) 10083 */ 10084 public static class MediaStyle extends Style { 10085 // Changing max media buttons requires also changing templates 10086 // (notification_template_material_media and notification_template_material_big_media). 10087 static final int MAX_MEDIA_BUTTONS_IN_COMPACT = 3; 10088 static final int MAX_MEDIA_BUTTONS = 5; 10089 @IdRes private static final int[] MEDIA_BUTTON_IDS = { 10090 R.id.action0, 10091 R.id.action1, 10092 R.id.action2, 10093 R.id.action3, 10094 R.id.action4, 10095 }; 10096 10097 private int[] mActionsToShowInCompact = null; 10098 private MediaSession.Token mToken; 10099 private CharSequence mDeviceName; 10100 private int mDeviceIcon; 10101 private PendingIntent mDeviceIntent; 10102 MediaStyle()10103 public MediaStyle() { 10104 } 10105 10106 /** 10107 * @deprecated use {@code MediaStyle()}. 10108 */ 10109 @Deprecated MediaStyle(Builder builder)10110 public MediaStyle(Builder builder) { 10111 setBuilder(builder); 10112 } 10113 10114 /** 10115 * Request up to 3 actions (by index in the order of addition) to be shown in the compact 10116 * notification view. 10117 * 10118 * @param actions the indices of the actions to show in the compact notification view 10119 */ setShowActionsInCompactView(int...actions)10120 public MediaStyle setShowActionsInCompactView(int...actions) { 10121 mActionsToShowInCompact = actions; 10122 return this; 10123 } 10124 10125 /** 10126 * Attach a {@link android.media.session.MediaSession.Token} to this Notification 10127 * to provide additional playback information and control to the SystemUI. 10128 */ setMediaSession(MediaSession.Token token)10129 public MediaStyle setMediaSession(MediaSession.Token token) { 10130 mToken = token; 10131 return this; 10132 } 10133 10134 /** 10135 * For media notifications associated with playback on a remote device, provide device 10136 * information that will replace the default values for the output switcher chip on the 10137 * media control, as well as an intent to use when the output switcher chip is tapped, 10138 * on devices where this is supported. 10139 * <p> 10140 * This method is intended for system applications to provide information and/or 10141 * functionality that would otherwise be unavailable to the default output switcher because 10142 * the media originated on a remote device. 10143 * 10144 * @param deviceName The name of the remote device to display 10145 * @param iconResource Icon resource representing the device 10146 * @param chipIntent PendingIntent to send when the output switcher is tapped. May be 10147 * {@code null}, in which case the output switcher will be disabled. 10148 * This intent should open an Activity or it will be ignored. 10149 * @return MediaStyle 10150 */ 10151 @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) 10152 @NonNull setRemotePlaybackInfo(@onNull CharSequence deviceName, @DrawableRes int iconResource, @Nullable PendingIntent chipIntent)10153 public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName, 10154 @DrawableRes int iconResource, @Nullable PendingIntent chipIntent) { 10155 mDeviceName = deviceName; 10156 mDeviceIcon = iconResource; 10157 mDeviceIntent = chipIntent; 10158 return this; 10159 } 10160 10161 /** 10162 * @hide 10163 */ 10164 @Override 10165 @UnsupportedAppUsage buildStyled(Notification wip)10166 public Notification buildStyled(Notification wip) { 10167 super.buildStyled(wip); 10168 if (wip.category == null) { 10169 wip.category = Notification.CATEGORY_TRANSPORT; 10170 } 10171 return wip; 10172 } 10173 10174 /** 10175 * @hide 10176 */ 10177 @Override makeContentView(boolean increasedHeight)10178 public RemoteViews makeContentView(boolean increasedHeight) { 10179 return makeMediaContentView(null /* customContent */); 10180 } 10181 10182 /** 10183 * @hide 10184 */ 10185 @Override makeBigContentView()10186 public RemoteViews makeBigContentView() { 10187 return makeMediaBigContentView(null /* customContent */); 10188 } 10189 10190 /** 10191 * @hide 10192 */ 10193 @Override makeHeadsUpContentView(boolean increasedHeight)10194 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 10195 return makeMediaContentView(null /* customContent */); 10196 } 10197 10198 /** @hide */ 10199 @Override addExtras(Bundle extras)10200 public void addExtras(Bundle extras) { 10201 super.addExtras(extras); 10202 10203 if (mToken != null) { 10204 extras.putParcelable(EXTRA_MEDIA_SESSION, mToken); 10205 } 10206 if (mActionsToShowInCompact != null) { 10207 extras.putIntArray(EXTRA_COMPACT_ACTIONS, mActionsToShowInCompact); 10208 } 10209 if (mDeviceName != null) { 10210 extras.putCharSequence(EXTRA_MEDIA_REMOTE_DEVICE, mDeviceName); 10211 } 10212 if (mDeviceIcon > 0) { 10213 extras.putInt(EXTRA_MEDIA_REMOTE_ICON, mDeviceIcon); 10214 } 10215 if (mDeviceIntent != null) { 10216 extras.putParcelable(EXTRA_MEDIA_REMOTE_INTENT, mDeviceIntent); 10217 } 10218 } 10219 10220 /** 10221 * @hide 10222 */ 10223 @Override restoreFromExtras(Bundle extras)10224 protected void restoreFromExtras(Bundle extras) { 10225 super.restoreFromExtras(extras); 10226 10227 if (extras.containsKey(EXTRA_MEDIA_SESSION)) { 10228 mToken = extras.getParcelable(EXTRA_MEDIA_SESSION, MediaSession.Token.class); 10229 } 10230 if (extras.containsKey(EXTRA_COMPACT_ACTIONS)) { 10231 mActionsToShowInCompact = extras.getIntArray(EXTRA_COMPACT_ACTIONS); 10232 } 10233 if (extras.containsKey(EXTRA_MEDIA_REMOTE_DEVICE)) { 10234 mDeviceName = extras.getCharSequence(EXTRA_MEDIA_REMOTE_DEVICE); 10235 } 10236 if (extras.containsKey(EXTRA_MEDIA_REMOTE_ICON)) { 10237 mDeviceIcon = extras.getInt(EXTRA_MEDIA_REMOTE_ICON); 10238 } 10239 if (extras.containsKey(EXTRA_MEDIA_REMOTE_INTENT)) { 10240 mDeviceIntent = extras.getParcelable( 10241 EXTRA_MEDIA_REMOTE_INTENT, PendingIntent.class); 10242 } 10243 } 10244 10245 /** 10246 * @hide 10247 */ 10248 @Override areNotificationsVisiblyDifferent(Style other)10249 public boolean areNotificationsVisiblyDifferent(Style other) { 10250 if (other == null || getClass() != other.getClass()) { 10251 return true; 10252 } 10253 // All fields to compare are on the Notification object 10254 return false; 10255 } 10256 bindMediaActionButton(RemoteViews container, @IdRes int buttonId, Action action, StandardTemplateParams p)10257 private void bindMediaActionButton(RemoteViews container, @IdRes int buttonId, 10258 Action action, StandardTemplateParams p) { 10259 final boolean tombstone = (action.actionIntent == null); 10260 container.setViewVisibility(buttonId, View.VISIBLE); 10261 container.setImageViewIcon(buttonId, action.getIcon()); 10262 10263 // If the action buttons should not be tinted, then just use the default 10264 // notification color. Otherwise, just use the passed-in color. 10265 int tintColor = mBuilder.getStandardActionColor(p); 10266 10267 container.setDrawableTint(buttonId, false, tintColor, 10268 PorterDuff.Mode.SRC_ATOP); 10269 10270 int rippleAlpha = mBuilder.getColors(p).getRippleAlpha(); 10271 int rippleColor = Color.argb(rippleAlpha, Color.red(tintColor), Color.green(tintColor), 10272 Color.blue(tintColor)); 10273 container.setRippleDrawableColor(buttonId, ColorStateList.valueOf(rippleColor)); 10274 10275 if (!tombstone) { 10276 container.setOnClickPendingIntent(buttonId, action.actionIntent); 10277 } 10278 container.setContentDescription(buttonId, action.title); 10279 } 10280 10281 /** @hide */ makeMediaContentView(@ullable RemoteViews customContent)10282 protected RemoteViews makeMediaContentView(@Nullable RemoteViews customContent) { 10283 final int numActions = mBuilder.mActions.size(); 10284 final int numActionsToShow = Math.min(mActionsToShowInCompact == null 10285 ? 0 : mActionsToShowInCompact.length, MAX_MEDIA_BUTTONS_IN_COMPACT); 10286 if (numActionsToShow > numActions) { 10287 throw new IllegalArgumentException(String.format( 10288 "setShowActionsInCompactView: action %d out of bounds (max %d)", 10289 numActions, numActions - 1)); 10290 } 10291 10292 StandardTemplateParams p = mBuilder.mParams.reset() 10293 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 10294 .hideTime(numActionsToShow > 1) // hide if actions wider than a right icon 10295 .hideSubText(numActionsToShow > 1) // hide if actions wider than a right icon 10296 .hideLeftIcon(false) // allow large icon on left when grouped 10297 .hideRightIcon(numActionsToShow > 0) // right icon or actions; not both 10298 .hideProgress(true) 10299 .fillTextsFrom(mBuilder); 10300 TemplateBindResult result = new TemplateBindResult(); 10301 RemoteViews template = mBuilder.applyStandardTemplate( 10302 R.layout.notification_template_material_media, p, 10303 null /* result */); 10304 10305 for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) { 10306 if (i < numActionsToShow) { 10307 final Action action = mBuilder.mActions.get(mActionsToShowInCompact[i]); 10308 bindMediaActionButton(template, MEDIA_BUTTON_IDS[i], action, p); 10309 } else { 10310 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 10311 } 10312 } 10313 // Prevent a swooping expand animation when there are no actions 10314 boolean hasActions = numActionsToShow != 0; 10315 template.setViewVisibility(R.id.media_actions, hasActions ? View.VISIBLE : View.GONE); 10316 10317 // Add custom view if provided by subclass. 10318 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 10319 return template; 10320 } 10321 10322 /** @hide */ makeMediaBigContentView(@ullable RemoteViews customContent)10323 protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) { 10324 final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS); 10325 StandardTemplateParams p = mBuilder.mParams.reset() 10326 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 10327 .hideProgress(true) 10328 .fillTextsFrom(mBuilder); 10329 TemplateBindResult result = new TemplateBindResult(); 10330 RemoteViews template = mBuilder.applyStandardTemplate( 10331 R.layout.notification_template_material_big_media, p , result); 10332 10333 for (int i = 0; i < MAX_MEDIA_BUTTONS; i++) { 10334 if (i < actionCount) { 10335 bindMediaActionButton(template, 10336 MEDIA_BUTTON_IDS[i], mBuilder.mActions.get(i), p); 10337 } else { 10338 template.setViewVisibility(MEDIA_BUTTON_IDS[i], View.GONE); 10339 } 10340 } 10341 buildCustomContentIntoTemplate(mBuilder.mContext, template, customContent, p, result); 10342 return template; 10343 } 10344 } 10345 10346 /** 10347 * Helper class for generating large-format notifications that include a large image attachment. 10348 * 10349 * Here's how you'd set the <code>CallStyle</code> on a notification: 10350 * <pre class="prettyprint"> 10351 * Notification notif = new Notification.Builder(mContext) 10352 * .setSmallIcon(R.drawable.new_post) 10353 * .setStyle(Notification.CallStyle.forIncomingCall(caller, declineIntent, answerIntent)) 10354 * .build(); 10355 * </pre> 10356 */ 10357 public static class CallStyle extends Style { 10358 /** 10359 * @hide 10360 */ 10361 public static final boolean DEBUG_NEW_ACTION_LAYOUT = true; 10362 10363 /** 10364 * @hide 10365 */ 10366 @Retention(RetentionPolicy.SOURCE) 10367 @IntDef({ 10368 CALL_TYPE_UNKNOWN, 10369 CALL_TYPE_INCOMING, 10370 CALL_TYPE_ONGOING, 10371 CALL_TYPE_SCREENING 10372 }) 10373 public @interface CallType {}; 10374 10375 /** 10376 * Unknown call type. 10377 * 10378 * See {@link #EXTRA_CALL_TYPE}. 10379 */ 10380 public static final int CALL_TYPE_UNKNOWN = 0; 10381 10382 /** 10383 * Call type for incoming calls. 10384 * 10385 * See {@link #EXTRA_CALL_TYPE}. 10386 */ 10387 public static final int CALL_TYPE_INCOMING = 1; 10388 /** 10389 * Call type for ongoing calls. 10390 * 10391 * See {@link #EXTRA_CALL_TYPE}. 10392 */ 10393 public static final int CALL_TYPE_ONGOING = 2; 10394 /** 10395 * Call type for calls that are being screened. 10396 * 10397 * See {@link #EXTRA_CALL_TYPE}. 10398 */ 10399 public static final int CALL_TYPE_SCREENING = 3; 10400 10401 /** 10402 * This is a key used privately on the action.extras to give spacing priority 10403 * to the required call actions 10404 */ 10405 private static final String KEY_ACTION_PRIORITY = "key_action_priority"; 10406 10407 private int mCallType; 10408 private Person mPerson; 10409 private PendingIntent mAnswerIntent; 10410 private PendingIntent mDeclineIntent; 10411 private PendingIntent mHangUpIntent; 10412 private boolean mIsVideo; 10413 private Integer mAnswerButtonColor; 10414 private Integer mDeclineButtonColor; 10415 private Icon mVerificationIcon; 10416 private CharSequence mVerificationText; 10417 CallStyle()10418 CallStyle() { 10419 } 10420 10421 /** 10422 * Create a CallStyle for an incoming call. 10423 * This notification will have a decline and an answer action, will allow a single 10424 * custom {@link Builder#addAction(Action) action}, and will have a default 10425 * {@link Builder#setContentText(CharSequence) content text} for an incoming call. 10426 * 10427 * @param person The person displayed as the caller. 10428 * The person also needs to have a non-empty name associated with it. 10429 * @param declineIntent The intent to be sent when the user taps the decline action 10430 * @param answerIntent The intent to be sent when the user taps the answer action 10431 */ 10432 @NonNull forIncomingCall(@onNull Person person, @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent)10433 public static CallStyle forIncomingCall(@NonNull Person person, 10434 @NonNull PendingIntent declineIntent, @NonNull PendingIntent answerIntent) { 10435 return new CallStyle(CALL_TYPE_INCOMING, person, 10436 null /* hangUpIntent */, 10437 requireNonNull(declineIntent, "declineIntent is required"), 10438 requireNonNull(answerIntent, "answerIntent is required") 10439 ); 10440 } 10441 10442 /** 10443 * Create a CallStyle for an ongoing call. 10444 * This notification will have a hang up action, will allow up to two 10445 * custom {@link Builder#addAction(Action) actions}, and will have a default 10446 * {@link Builder#setContentText(CharSequence) content text} for an ongoing call. 10447 * 10448 * @param person The person displayed as being on the other end of the call. 10449 * The person also needs to have a non-empty name associated with it. 10450 * @param hangUpIntent The intent to be sent when the user taps the hang up action 10451 */ 10452 @NonNull forOngoingCall(@onNull Person person, @NonNull PendingIntent hangUpIntent)10453 public static CallStyle forOngoingCall(@NonNull Person person, 10454 @NonNull PendingIntent hangUpIntent) { 10455 return new CallStyle(CALL_TYPE_ONGOING, person, 10456 requireNonNull(hangUpIntent, "hangUpIntent is required"), 10457 null /* declineIntent */, 10458 null /* answerIntent */ 10459 ); 10460 } 10461 10462 /** 10463 * Create a CallStyle for a call that is being screened. 10464 * This notification will have a hang up and an answer action, will allow a single 10465 * custom {@link Builder#addAction(Action) action}, and will have a default 10466 * {@link Builder#setContentText(CharSequence) content text} for a call that is being 10467 * screened. 10468 * 10469 * @param person The person displayed as the caller. 10470 * The person also needs to have a non-empty name associated with it. 10471 * @param hangUpIntent The intent to be sent when the user taps the hang up action 10472 * @param answerIntent The intent to be sent when the user taps the answer action 10473 */ 10474 @NonNull forScreeningCall(@onNull Person person, @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent)10475 public static CallStyle forScreeningCall(@NonNull Person person, 10476 @NonNull PendingIntent hangUpIntent, @NonNull PendingIntent answerIntent) { 10477 return new CallStyle(CALL_TYPE_SCREENING, person, 10478 requireNonNull(hangUpIntent, "hangUpIntent is required"), 10479 null /* declineIntent */, 10480 requireNonNull(answerIntent, "answerIntent is required") 10481 ); 10482 } 10483 10484 /** 10485 * @param callType The type of the call 10486 * @param person The person displayed for the incoming call. 10487 * The user also needs to have a non-empty name associated with it. 10488 * @param hangUpIntent The intent to be sent when the user taps the hang up action 10489 * @param declineIntent The intent to be sent when the user taps the decline action 10490 * @param answerIntent The intent to be sent when the user taps the answer action 10491 */ CallStyle(@allType int callType, @NonNull Person person, @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, @Nullable PendingIntent answerIntent)10492 private CallStyle(@CallType int callType, @NonNull Person person, 10493 @Nullable PendingIntent hangUpIntent, @Nullable PendingIntent declineIntent, 10494 @Nullable PendingIntent answerIntent) { 10495 if (person == null || TextUtils.isEmpty(person.getName())) { 10496 throw new IllegalArgumentException("person must have a non-empty a name"); 10497 } 10498 mCallType = callType; 10499 mPerson = person; 10500 mAnswerIntent = answerIntent; 10501 mDeclineIntent = declineIntent; 10502 mHangUpIntent = hangUpIntent; 10503 } 10504 10505 /** 10506 * Sets whether the call is a video call, which may affect the icons or text used on the 10507 * required action buttons. 10508 */ 10509 @NonNull setIsVideo(boolean isVideo)10510 public CallStyle setIsVideo(boolean isVideo) { 10511 mIsVideo = isVideo; 10512 return this; 10513 } 10514 10515 /** 10516 * Optional icon to be displayed with {@link #setVerificationText(CharSequence) text} 10517 * as a verification status of the caller. 10518 */ 10519 @NonNull setVerificationIcon(@ullable Icon verificationIcon)10520 public CallStyle setVerificationIcon(@Nullable Icon verificationIcon) { 10521 mVerificationIcon = verificationIcon; 10522 return this; 10523 } 10524 10525 /** 10526 * Optional text to be displayed with an {@link #setVerificationIcon(Icon) icon} 10527 * as a verification status of the caller. 10528 */ 10529 @NonNull setVerificationText(@ullable CharSequence verificationText)10530 public CallStyle setVerificationText(@Nullable CharSequence verificationText) { 10531 mVerificationText = safeCharSequence(verificationText); 10532 return this; 10533 } 10534 10535 /** 10536 * Optional color to be used as a hint for the Answer action button's color. 10537 * The system may change this color to ensure sufficient contrast with the background. 10538 * The system may choose to disregard this hint if the notification is not colorized. 10539 */ 10540 @NonNull setAnswerButtonColorHint(@olorInt int color)10541 public CallStyle setAnswerButtonColorHint(@ColorInt int color) { 10542 mAnswerButtonColor = color; 10543 return this; 10544 } 10545 10546 /** 10547 * Optional color to be used as a hint for the Decline or Hang Up action button's color. 10548 * The system may change this color to ensure sufficient contrast with the background. 10549 * The system may choose to disregard this hint if the notification is not colorized. 10550 */ 10551 @NonNull setDeclineButtonColorHint(@olorInt int color)10552 public CallStyle setDeclineButtonColorHint(@ColorInt int color) { 10553 mDeclineButtonColor = color; 10554 return this; 10555 } 10556 10557 /** @hide */ 10558 @Override buildStyled(Notification wip)10559 public Notification buildStyled(Notification wip) { 10560 wip = super.buildStyled(wip); 10561 // ensure that the actions in the builder and notification are corrected. 10562 mBuilder.mActions = getActionsListWithSystemActions(); 10563 wip.actions = new Action[mBuilder.mActions.size()]; 10564 mBuilder.mActions.toArray(wip.actions); 10565 return wip; 10566 } 10567 10568 /** 10569 * @hide 10570 */ displayCustomViewInline()10571 public boolean displayCustomViewInline() { 10572 // This is a lie; True is returned to make sure that the custom view is not used 10573 // instead of the template, but it will not actually be included. 10574 return true; 10575 } 10576 10577 /** 10578 * @hide 10579 */ 10580 @Override purgeResources()10581 public void purgeResources() { 10582 super.purgeResources(); 10583 if (mVerificationIcon != null) { 10584 mVerificationIcon.convertToAshmem(); 10585 } 10586 } 10587 10588 /** 10589 * @hide 10590 */ 10591 @Override reduceImageSizes(Context context)10592 public void reduceImageSizes(Context context) { 10593 super.reduceImageSizes(context); 10594 if (mVerificationIcon != null) { 10595 int rightIconSize = context.getResources().getDimensionPixelSize( 10596 ActivityManager.isLowRamDeviceStatic() 10597 ? R.dimen.notification_right_icon_size_low_ram 10598 : R.dimen.notification_right_icon_size); 10599 mVerificationIcon.scaleDownIfNecessary(rightIconSize, rightIconSize); 10600 } 10601 } 10602 10603 /** 10604 * @hide 10605 */ 10606 @Override makeContentView(boolean increasedHeight)10607 public RemoteViews makeContentView(boolean increasedHeight) { 10608 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL); 10609 } 10610 10611 /** 10612 * @hide 10613 */ 10614 @Override makeHeadsUpContentView(boolean increasedHeight)10615 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 10616 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP); 10617 } 10618 10619 /** 10620 * @hide 10621 */ 10622 @Nullable 10623 @Override makeCompactHeadsUpContentView()10624 public RemoteViews makeCompactHeadsUpContentView() { 10625 // Use existing heads up for call style. 10626 return makeHeadsUpContentView(false); 10627 } 10628 10629 /** 10630 * @hide 10631 */ makeBigContentView()10632 public RemoteViews makeBigContentView() { 10633 return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG); 10634 } 10635 10636 @NonNull makeNegativeAction()10637 private Action makeNegativeAction() { 10638 if (mDeclineIntent == null) { 10639 return makeAction(R.drawable.ic_call_decline, 10640 R.string.call_notification_hang_up_action, 10641 mDeclineButtonColor, R.color.call_notification_decline_color, 10642 mHangUpIntent); 10643 } else { 10644 return makeAction(R.drawable.ic_call_decline, 10645 R.string.call_notification_decline_action, 10646 mDeclineButtonColor, R.color.call_notification_decline_color, 10647 mDeclineIntent); 10648 } 10649 } 10650 10651 @Nullable makeAnswerAction()10652 private Action makeAnswerAction() { 10653 return mAnswerIntent == null ? null : makeAction( 10654 mIsVideo ? R.drawable.ic_call_answer_video : R.drawable.ic_call_answer, 10655 mIsVideo ? R.string.call_notification_answer_video_action 10656 : R.string.call_notification_answer_action, 10657 mAnswerButtonColor, R.color.call_notification_answer_color, 10658 mAnswerIntent); 10659 } 10660 10661 @NonNull makeAction(@rawableRes int icon, @StringRes int title, @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent)10662 private Action makeAction(@DrawableRes int icon, @StringRes int title, 10663 @ColorInt Integer colorInt, @ColorRes int defaultColorRes, PendingIntent intent) { 10664 if (colorInt == null || !mBuilder.isCallActionColorCustomizable()) { 10665 colorInt = mBuilder.mContext.getColor(defaultColorRes); 10666 } 10667 Action action = new Action.Builder(Icon.createWithResource("", icon), 10668 new SpannableStringBuilder().append(mBuilder.mContext.getString(title), 10669 new ForegroundColorSpan(colorInt), 10670 SpannableStringBuilder.SPAN_INCLUSIVE_INCLUSIVE), 10671 intent).build(); 10672 action.getExtras().putBoolean(KEY_ACTION_PRIORITY, true); 10673 return action; 10674 } 10675 isActionAddedByCallStyle(Action action)10676 private boolean isActionAddedByCallStyle(Action action) { 10677 // This is an internal extra added by the style to these actions. If an app were to add 10678 // this extra to the action themselves, the action would be dropped. :shrug: 10679 return action != null && action.getExtras().getBoolean(KEY_ACTION_PRIORITY); 10680 } 10681 10682 /** 10683 * Gets the actions list for the call with the answer/decline/hangUp actions inserted in 10684 * the correct place. This returns the correct result even if the system actions have 10685 * already been added, and even if more actions were added since then. 10686 * @hide 10687 */ 10688 @NonNull getActionsListWithSystemActions()10689 public ArrayList<Action> getActionsListWithSystemActions() { 10690 // Define the system actions we expect to see 10691 final Action firstAction = makeNegativeAction(); 10692 final Action lastAction = makeAnswerAction(); 10693 10694 // Start creating the result list. 10695 int nonContextualActionSlotsRemaining = MAX_ACTION_BUTTONS; 10696 ArrayList<Action> resultActions = new ArrayList<>(MAX_ACTION_BUTTONS); 10697 10698 // Always have a first action. 10699 resultActions.add(firstAction); 10700 --nonContextualActionSlotsRemaining; 10701 10702 // Copy actions into the new list, correcting system actions. 10703 if (mBuilder.mActions != null) { 10704 for (Notification.Action action : mBuilder.mActions) { 10705 if (action.isContextual()) { 10706 // Always include all contextual actions 10707 resultActions.add(action); 10708 } else if (isActionAddedByCallStyle(action)) { 10709 // Drop any old versions of system actions 10710 } else { 10711 // Copy non-contextual actions; decrement the remaining action slots. 10712 resultActions.add(action); 10713 --nonContextualActionSlotsRemaining; 10714 } 10715 // If there's exactly one action slot left, fill it with the lastAction. 10716 if (lastAction != null && nonContextualActionSlotsRemaining == 1) { 10717 resultActions.add(lastAction); 10718 --nonContextualActionSlotsRemaining; 10719 } 10720 } 10721 } 10722 // If there are any action slots left, the lastAction still needs to be added. 10723 if (lastAction != null && nonContextualActionSlotsRemaining >= 1) { 10724 resultActions.add(lastAction); 10725 } 10726 return resultActions; 10727 } 10728 makeCallLayout(int viewType)10729 private RemoteViews makeCallLayout(int viewType) { 10730 final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL; 10731 Bundle extras = mBuilder.mN.extras; 10732 CharSequence title = mPerson != null ? mPerson.getName() : null; 10733 CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 10734 if (text == null) { 10735 text = getDefaultText(); 10736 } 10737 10738 // Bind standard template 10739 StandardTemplateParams p = mBuilder.mParams.reset() 10740 .viewType(viewType) 10741 .callStyleActions(true) 10742 .allowTextWithProgress(true) 10743 .hideLeftIcon(true) 10744 .hideRightIcon(true) 10745 .hideAppName(isCollapsed) 10746 .titleViewId(R.id.conversation_text) 10747 .title(title) 10748 .text(text) 10749 .summaryText(mBuilder.processLegacyText(mVerificationText)); 10750 mBuilder.mActions = getActionsListWithSystemActions(); 10751 final RemoteViews contentView; 10752 if (isCollapsed) { 10753 contentView = mBuilder.applyStandardTemplate( 10754 R.layout.notification_template_material_call, p, null /* result */); 10755 } else { 10756 contentView = mBuilder.applyStandardTemplateWithActions( 10757 R.layout.notification_template_material_big_call, p, null /* result */); 10758 } 10759 10760 // Bind some extra conversation-specific header fields. 10761 if (!p.mHideAppName) { 10762 mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p); 10763 contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE); 10764 } 10765 bindCallerVerification(contentView, p); 10766 10767 // Bind some custom CallLayout properties 10768 contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", 10769 mBuilder.getSmallIconColor(p)); 10770 contentView.setInt(R.id.status_bar_latest_event_content, 10771 "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); 10772 contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon", 10773 mBuilder.mN.mLargeIcon); 10774 contentView.setBundle(R.id.status_bar_latest_event_content, "setData", 10775 mBuilder.mN.extras); 10776 10777 return contentView; 10778 } 10779 bindCallerVerification(RemoteViews contentView, StandardTemplateParams p)10780 private void bindCallerVerification(RemoteViews contentView, StandardTemplateParams p) { 10781 String iconContentDescription = null; 10782 boolean showDivider = true; 10783 if (mVerificationIcon != null) { 10784 contentView.setImageViewIcon(R.id.verification_icon, mVerificationIcon); 10785 contentView.setDrawableTint(R.id.verification_icon, false /* targetBackground */, 10786 mBuilder.getSecondaryTextColor(p), PorterDuff.Mode.SRC_ATOP); 10787 contentView.setViewVisibility(R.id.verification_icon, View.VISIBLE); 10788 iconContentDescription = mBuilder.mContext.getString( 10789 R.string.notification_verified_content_description); 10790 showDivider = false; // the icon replaces the divider 10791 } else { 10792 contentView.setViewVisibility(R.id.verification_icon, View.GONE); 10793 } 10794 if (!TextUtils.isEmpty(mVerificationText)) { 10795 contentView.setTextViewText(R.id.verification_text, mVerificationText); 10796 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_text, p); 10797 contentView.setViewVisibility(R.id.verification_text, View.VISIBLE); 10798 iconContentDescription = null; // let the app's text take precedence 10799 } else { 10800 contentView.setViewVisibility(R.id.verification_text, View.GONE); 10801 showDivider = false; // no divider if no text 10802 } 10803 contentView.setContentDescription(R.id.verification_icon, iconContentDescription); 10804 if (showDivider) { 10805 contentView.setViewVisibility(R.id.verification_divider, View.VISIBLE); 10806 mBuilder.setTextViewColorSecondary(contentView, R.id.verification_divider, p); 10807 } else { 10808 contentView.setViewVisibility(R.id.verification_divider, View.GONE); 10809 } 10810 } 10811 10812 @Nullable getDefaultText()10813 private String getDefaultText() { 10814 switch (mCallType) { 10815 case CALL_TYPE_INCOMING: 10816 return mBuilder.mContext.getString(R.string.call_notification_incoming_text); 10817 case CALL_TYPE_ONGOING: 10818 return mBuilder.mContext.getString(R.string.call_notification_ongoing_text); 10819 case CALL_TYPE_SCREENING: 10820 return mBuilder.mContext.getString(R.string.call_notification_screening_text); 10821 } 10822 return null; 10823 } 10824 10825 /** 10826 * @hide 10827 */ addExtras(Bundle extras)10828 public void addExtras(Bundle extras) { 10829 super.addExtras(extras); 10830 extras.putInt(EXTRA_CALL_TYPE, mCallType); 10831 extras.putBoolean(EXTRA_CALL_IS_VIDEO, mIsVideo); 10832 extras.putParcelable(EXTRA_CALL_PERSON, mPerson); 10833 if (mVerificationIcon != null) { 10834 extras.putParcelable(EXTRA_VERIFICATION_ICON, mVerificationIcon); 10835 } 10836 if (mVerificationText != null) { 10837 extras.putCharSequence(EXTRA_VERIFICATION_TEXT, mVerificationText); 10838 } 10839 if (mAnswerIntent != null) { 10840 extras.putParcelable(EXTRA_ANSWER_INTENT, mAnswerIntent); 10841 } 10842 if (mDeclineIntent != null) { 10843 extras.putParcelable(EXTRA_DECLINE_INTENT, mDeclineIntent); 10844 } 10845 if (mHangUpIntent != null) { 10846 extras.putParcelable(EXTRA_HANG_UP_INTENT, mHangUpIntent); 10847 } 10848 if (mAnswerButtonColor != null) { 10849 extras.putInt(EXTRA_ANSWER_COLOR, mAnswerButtonColor); 10850 } 10851 if (mDeclineButtonColor != null) { 10852 extras.putInt(EXTRA_DECLINE_COLOR, mDeclineButtonColor); 10853 } 10854 fixTitleAndTextExtras(extras); 10855 } 10856 fixTitleAndTextExtras(Bundle extras)10857 private void fixTitleAndTextExtras(Bundle extras) { 10858 CharSequence sender = mPerson != null ? mPerson.getName() : null; 10859 if (sender != null) { 10860 extras.putCharSequence(EXTRA_TITLE, sender); 10861 } 10862 if (extras.getCharSequence(EXTRA_TEXT) == null) { 10863 extras.putCharSequence(EXTRA_TEXT, getDefaultText()); 10864 } 10865 } 10866 10867 /** 10868 * @hide 10869 */ 10870 @Override restoreFromExtras(Bundle extras)10871 protected void restoreFromExtras(Bundle extras) { 10872 super.restoreFromExtras(extras); 10873 mCallType = extras.getInt(EXTRA_CALL_TYPE); 10874 mIsVideo = extras.getBoolean(EXTRA_CALL_IS_VIDEO); 10875 mPerson = extras.getParcelable(EXTRA_CALL_PERSON, Person.class); 10876 mVerificationIcon = extras.getParcelable(EXTRA_VERIFICATION_ICON, android.graphics.drawable.Icon.class); 10877 mVerificationText = extras.getCharSequence(EXTRA_VERIFICATION_TEXT); 10878 mAnswerIntent = extras.getParcelable(EXTRA_ANSWER_INTENT, PendingIntent.class); 10879 mDeclineIntent = extras.getParcelable(EXTRA_DECLINE_INTENT, PendingIntent.class); 10880 mHangUpIntent = extras.getParcelable(EXTRA_HANG_UP_INTENT, PendingIntent.class); 10881 mAnswerButtonColor = extras.containsKey(EXTRA_ANSWER_COLOR) 10882 ? extras.getInt(EXTRA_ANSWER_COLOR) : null; 10883 mDeclineButtonColor = extras.containsKey(EXTRA_DECLINE_COLOR) 10884 ? extras.getInt(EXTRA_DECLINE_COLOR) : null; 10885 } 10886 10887 /** 10888 * @hide 10889 */ 10890 @Override hasSummaryInHeader()10891 public boolean hasSummaryInHeader() { 10892 return false; 10893 } 10894 10895 /** 10896 * @hide 10897 */ 10898 @Override areNotificationsVisiblyDifferent(Style other)10899 public boolean areNotificationsVisiblyDifferent(Style other) { 10900 if (other == null || getClass() != other.getClass()) { 10901 return true; 10902 } 10903 CallStyle otherS = (CallStyle) other; 10904 return !Objects.equals(mCallType, otherS.mCallType) 10905 || !Objects.equals(mPerson, otherS.mPerson) 10906 || !Objects.equals(mVerificationText, otherS.mVerificationText); 10907 } 10908 } 10909 10910 /** 10911 * Notification style for custom views that are decorated by the system 10912 * 10913 * <p>Instead of providing a notification that is completely custom, a developer can set this 10914 * style and still obtain system decorations like the notification header with the expand 10915 * affordance and actions. 10916 * 10917 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 10918 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 10919 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 10920 * corresponding custom views to display. 10921 * 10922 * To use this style with your Notification, feed it to 10923 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 10924 * <pre class="prettyprint"> 10925 * Notification noti = new Notification.Builder() 10926 * .setSmallIcon(R.drawable.ic_stat_player) 10927 * .setLargeIcon(albumArtBitmap)) 10928 * .setCustomContentView(contentView); 10929 * .setStyle(<b>new Notification.DecoratedCustomViewStyle()</b>) 10930 * .build(); 10931 * </pre> 10932 */ 10933 public static class DecoratedCustomViewStyle extends Style { 10934 DecoratedCustomViewStyle()10935 public DecoratedCustomViewStyle() { 10936 } 10937 10938 /** 10939 * @hide 10940 */ displayCustomViewInline()10941 public boolean displayCustomViewInline() { 10942 return true; 10943 } 10944 10945 /** 10946 * @hide 10947 */ 10948 @Override makeContentView(boolean increasedHeight)10949 public RemoteViews makeContentView(boolean increasedHeight) { 10950 return makeStandardTemplateWithCustomContent(mBuilder.mN.contentView); 10951 } 10952 10953 /** 10954 * @hide 10955 */ 10956 @Override makeBigContentView()10957 public RemoteViews makeBigContentView() { 10958 return makeDecoratedBigContentView(); 10959 } 10960 10961 /** 10962 * @hide 10963 */ 10964 @Override makeHeadsUpContentView(boolean increasedHeight)10965 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 10966 return makeDecoratedHeadsUpContentView(); 10967 } 10968 makeDecoratedHeadsUpContentView()10969 private RemoteViews makeDecoratedHeadsUpContentView() { 10970 RemoteViews headsUpContentView = mBuilder.mN.headsUpContentView == null 10971 ? mBuilder.mN.contentView 10972 : mBuilder.mN.headsUpContentView; 10973 if (headsUpContentView == null) { 10974 return null; // no custom view; use the default behavior 10975 } 10976 if (mBuilder.mActions.size() == 0) { 10977 return makeStandardTemplateWithCustomContent(headsUpContentView); 10978 } 10979 TemplateBindResult result = new TemplateBindResult(); 10980 StandardTemplateParams p = mBuilder.mParams.reset() 10981 .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) 10982 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 10983 .fillTextsFrom(mBuilder); 10984 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 10985 mBuilder.getHeadsUpBaseLayoutResource(), p, result); 10986 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, headsUpContentView, 10987 p, result); 10988 return remoteViews; 10989 } 10990 makeStandardTemplateWithCustomContent(RemoteViews customContent)10991 private RemoteViews makeStandardTemplateWithCustomContent(RemoteViews customContent) { 10992 if (customContent == null) { 10993 return null; // no custom view; use the default behavior 10994 } 10995 TemplateBindResult result = new TemplateBindResult(); 10996 StandardTemplateParams p = mBuilder.mParams.reset() 10997 .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL) 10998 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 10999 .fillTextsFrom(mBuilder); 11000 RemoteViews remoteViews = mBuilder.applyStandardTemplate( 11001 mBuilder.getBaseLayoutResource(), p, result); 11002 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent, 11003 p, result); 11004 return remoteViews; 11005 } 11006 makeDecoratedBigContentView()11007 private RemoteViews makeDecoratedBigContentView() { 11008 RemoteViews bigContentView = mBuilder.mN.bigContentView == null 11009 ? mBuilder.mN.contentView 11010 : mBuilder.mN.bigContentView; 11011 if (bigContentView == null) { 11012 return null; // no custom view; use the default behavior 11013 } 11014 TemplateBindResult result = new TemplateBindResult(); 11015 StandardTemplateParams p = mBuilder.mParams.reset() 11016 .viewType(StandardTemplateParams.VIEW_TYPE_BIG) 11017 .decorationType(StandardTemplateParams.DECORATION_PARTIAL) 11018 .fillTextsFrom(mBuilder); 11019 RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions( 11020 mBuilder.getBigBaseLayoutResource(), p, result); 11021 buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView, 11022 p, result); 11023 return remoteViews; 11024 } 11025 11026 /** 11027 * @hide 11028 */ 11029 @Override areNotificationsVisiblyDifferent(Style other)11030 public boolean areNotificationsVisiblyDifferent(Style other) { 11031 if (other == null || getClass() != other.getClass()) { 11032 return true; 11033 } 11034 // Comparison done for all custom RemoteViews, independent of style 11035 return false; 11036 } 11037 } 11038 11039 /** 11040 * Notification style for media custom views that are decorated by the system 11041 * 11042 * <p>Instead of providing a media notification that is completely custom, a developer can set 11043 * this style and still obtain system decorations like the notification header with the expand 11044 * affordance and actions. 11045 * 11046 * <p>Use {@link android.app.Notification.Builder#setCustomContentView(RemoteViews)}, 11047 * {@link android.app.Notification.Builder#setCustomBigContentView(RemoteViews)} and 11048 * {@link android.app.Notification.Builder#setCustomHeadsUpContentView(RemoteViews)} to set the 11049 * corresponding custom views to display. 11050 * <p> 11051 * Contrary to {@link MediaStyle} a developer has to opt-in to the colorizing of the 11052 * notification by using {@link Notification.Builder#setColorized(boolean)}. 11053 * <p> 11054 * To use this style with your Notification, feed it to 11055 * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: 11056 * <pre class="prettyprint"> 11057 * Notification noti = new Notification.Builder() 11058 * .setSmallIcon(R.drawable.ic_stat_player) 11059 * .setLargeIcon(albumArtBitmap)) 11060 * .setCustomContentView(contentView); 11061 * .setStyle(<b>new Notification.DecoratedMediaCustomViewStyle()</b> 11062 * .setMediaSession(mySession)) 11063 * .build(); 11064 * </pre> 11065 * 11066 * @see android.app.Notification.DecoratedCustomViewStyle 11067 * @see android.app.Notification.MediaStyle 11068 */ 11069 public static class DecoratedMediaCustomViewStyle extends MediaStyle { 11070 DecoratedMediaCustomViewStyle()11071 public DecoratedMediaCustomViewStyle() { 11072 } 11073 11074 /** 11075 * @hide 11076 */ displayCustomViewInline()11077 public boolean displayCustomViewInline() { 11078 return true; 11079 } 11080 11081 /** 11082 * @hide 11083 */ 11084 @Override makeContentView(boolean increasedHeight)11085 public RemoteViews makeContentView(boolean increasedHeight) { 11086 return makeMediaContentView(mBuilder.mN.contentView); 11087 } 11088 11089 /** 11090 * @hide 11091 */ 11092 @Override makeBigContentView()11093 public RemoteViews makeBigContentView() { 11094 RemoteViews customContent = mBuilder.mN.bigContentView != null 11095 ? mBuilder.mN.bigContentView 11096 : mBuilder.mN.contentView; 11097 return makeMediaBigContentView(customContent); 11098 } 11099 11100 /** 11101 * @hide 11102 */ 11103 @Override makeHeadsUpContentView(boolean increasedHeight)11104 public RemoteViews makeHeadsUpContentView(boolean increasedHeight) { 11105 RemoteViews customContent = mBuilder.mN.headsUpContentView != null 11106 ? mBuilder.mN.headsUpContentView 11107 : mBuilder.mN.contentView; 11108 return makeMediaBigContentView(customContent); 11109 } 11110 11111 /** 11112 * @hide 11113 */ 11114 @Override areNotificationsVisiblyDifferent(Style other)11115 public boolean areNotificationsVisiblyDifferent(Style other) { 11116 if (other == null || getClass() != other.getClass()) { 11117 return true; 11118 } 11119 // Comparison done for all custom RemoteViews, independent of style 11120 return false; 11121 } 11122 } 11123 11124 /** 11125 * Encapsulates the information needed to display a notification as a bubble. 11126 * 11127 * <p>A bubble is used to display app content in a floating window over the existing 11128 * foreground activity. A bubble has a collapsed state represented by an icon and an 11129 * expanded state that displays an activity. These may be defined via 11130 * {@link Builder#Builder(PendingIntent, Icon)} or they may 11131 * be defined via an existing shortcut using {@link Builder#Builder(String)}. 11132 * </p> 11133 * 11134 * <b>Notifications with a valid and allowed bubble will display in collapsed state 11135 * outside of the notification shade on unlocked devices. When a user interacts with the 11136 * collapsed bubble, the bubble activity will be invoked and displayed.</b> 11137 * 11138 * @see Notification.Builder#setBubbleMetadata(BubbleMetadata) 11139 */ 11140 public static final class BubbleMetadata implements Parcelable { 11141 11142 private PendingIntent mPendingIntent; 11143 private PendingIntent mDeleteIntent; 11144 private Icon mIcon; 11145 private int mDesiredHeight; 11146 @DimenRes private int mDesiredHeightResId; 11147 private int mFlags; 11148 private String mShortcutId; 11149 11150 /** 11151 * If set and the app creating the bubble is in the foreground, the bubble will be posted 11152 * in its expanded state. 11153 * 11154 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 11155 * The app is considered foreground if it is visible and on the screen, note that 11156 * a foreground service does not qualify. 11157 * </p> 11158 * 11159 * <p>Generally this flag should only be set if the user has performed an action to request 11160 * or create a bubble.</p> 11161 * 11162 * @hide 11163 */ 11164 public static final int FLAG_AUTO_EXPAND_BUBBLE = 0x00000001; 11165 11166 /** 11167 * Indicates whether the notification associated with the bubble is being visually 11168 * suppressed from the notification shade. When <code>true</code> the notification is 11169 * hidden, when <code>false</code> the notification shows as normal. 11170 * 11171 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 11172 * the associated notification in the notification shade.</p> 11173 * 11174 * <p>Generally this flag should only be set by the app if the user has performed an 11175 * action to request or create a bubble, or if the user has seen the content in the 11176 * notification and the notification is no longer relevant. </p> 11177 * 11178 * <p>The system will also update this flag with <code>true</code> to hide the notification 11179 * from the user once the bubble has been expanded. </p> 11180 * 11181 * @hide 11182 */ 11183 public static final int FLAG_SUPPRESS_NOTIFICATION = 0x00000002; 11184 11185 /** 11186 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 11187 * user is viewing the same content outside of the bubble. For example, the user has a 11188 * bubble with Alice and then opens up the main app and navigates to Alice's page. 11189 * 11190 * @hide 11191 */ 11192 public static final int FLAG_SUPPRESSABLE_BUBBLE = 0x00000004; 11193 11194 /** 11195 * Indicates whether the bubble is visually suppressed from the bubble stack. 11196 * 11197 * @hide 11198 */ 11199 public static final int FLAG_SUPPRESS_BUBBLE = 0x00000008; 11200 BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, Icon icon, int height, @DimenRes int heightResId, String shortcutId)11201 private BubbleMetadata(PendingIntent expandIntent, PendingIntent deleteIntent, 11202 Icon icon, int height, @DimenRes int heightResId, String shortcutId) { 11203 mPendingIntent = expandIntent; 11204 mIcon = icon; 11205 mDesiredHeight = height; 11206 mDesiredHeightResId = heightResId; 11207 mDeleteIntent = deleteIntent; 11208 mShortcutId = shortcutId; 11209 } 11210 BubbleMetadata(Parcel in)11211 private BubbleMetadata(Parcel in) { 11212 if (in.readInt() != 0) { 11213 mPendingIntent = PendingIntent.CREATOR.createFromParcel(in); 11214 } 11215 if (in.readInt() != 0) { 11216 mIcon = Icon.CREATOR.createFromParcel(in); 11217 } 11218 mDesiredHeight = in.readInt(); 11219 mFlags = in.readInt(); 11220 if (in.readInt() != 0) { 11221 mDeleteIntent = PendingIntent.CREATOR.createFromParcel(in); 11222 } 11223 mDesiredHeightResId = in.readInt(); 11224 if (in.readInt() != 0) { 11225 mShortcutId = in.readString8(); 11226 } 11227 } 11228 11229 /** 11230 * @return the shortcut id used for this bubble if created via 11231 * {@link Builder#Builder(String)} or null if created 11232 * via {@link Builder#Builder(PendingIntent, Icon)}. 11233 */ 11234 @Nullable getShortcutId()11235 public String getShortcutId() { 11236 return mShortcutId; 11237 } 11238 11239 /** 11240 * @return the pending intent used to populate the floating window for this bubble, or 11241 * null if this bubble is created via {@link Builder#Builder(String)}. 11242 */ 11243 @SuppressLint("InvalidNullConversion") 11244 @Nullable getIntent()11245 public PendingIntent getIntent() { 11246 return mPendingIntent; 11247 } 11248 11249 /** 11250 * @return the pending intent to send when the bubble is dismissed by a user, if one exists. 11251 */ 11252 @Nullable getDeleteIntent()11253 public PendingIntent getDeleteIntent() { 11254 return mDeleteIntent; 11255 } 11256 11257 /** 11258 * @return the icon that will be displayed for this bubble when it is collapsed, or null 11259 * if the bubble is created via {@link Builder#Builder(String)}. 11260 */ 11261 @SuppressLint("InvalidNullConversion") 11262 @Nullable getIcon()11263 public Icon getIcon() { 11264 return mIcon; 11265 } 11266 11267 /** 11268 * @return the ideal height, in DPs, for the floating window that app content defined by 11269 * {@link #getIntent()} for this bubble. A value of 0 indicates a desired height has 11270 * not been set. 11271 */ 11272 @Dimension(unit = DP) getDesiredHeight()11273 public int getDesiredHeight() { 11274 return mDesiredHeight; 11275 } 11276 11277 /** 11278 * @return the resId of ideal height for the floating window that app content defined by 11279 * {@link #getIntent()} for this bubble. A value of 0 indicates a res value has not 11280 * been provided for the desired height. 11281 */ 11282 @DimenRes getDesiredHeightResId()11283 public int getDesiredHeightResId() { 11284 return mDesiredHeightResId; 11285 } 11286 11287 /** 11288 * @return whether this bubble should auto expand when it is posted. 11289 * 11290 * @see BubbleMetadata.Builder#setAutoExpandBubble(boolean) 11291 */ getAutoExpandBubble()11292 public boolean getAutoExpandBubble() { 11293 return (mFlags & FLAG_AUTO_EXPAND_BUBBLE) != 0; 11294 } 11295 11296 /** 11297 * Indicates whether the notification associated with the bubble is being visually 11298 * suppressed from the notification shade. When <code>true</code> the notification is 11299 * hidden, when <code>false</code> the notification shows as normal. 11300 * 11301 * <p>Apps sending bubbles may set this flag so that the bubble is posted <b>without</b> 11302 * the associated notification in the notification shade.</p> 11303 * 11304 * <p>Generally the app should only set this flag if the user has performed an 11305 * action to request or create a bubble, or if the user has seen the content in the 11306 * notification and the notification is no longer relevant. </p> 11307 * 11308 * <p>The system will update this flag with <code>true</code> to hide the notification 11309 * from the user once the bubble has been expanded.</p> 11310 * 11311 * @return whether this bubble should suppress the notification when it is posted. 11312 * 11313 * @see BubbleMetadata.Builder#setSuppressNotification(boolean) 11314 */ isNotificationSuppressed()11315 public boolean isNotificationSuppressed() { 11316 return (mFlags & FLAG_SUPPRESS_NOTIFICATION) != 0; 11317 } 11318 11319 /** 11320 * Indicates whether the bubble should be visually suppressed from the bubble stack if the 11321 * user is viewing the same content outside of the bubble. For example, the user has a 11322 * bubble with Alice and then opens up the main app and navigates to Alice's page. 11323 * 11324 * To match the activity and the bubble notification, the bubble notification should 11325 * have a locus id set that matches a locus id set on the activity. 11326 * 11327 * @return whether this bubble should be suppressed when the same content is visible 11328 * outside of the bubble. 11329 * 11330 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 11331 */ isBubbleSuppressable()11332 public boolean isBubbleSuppressable() { 11333 return (mFlags & FLAG_SUPPRESSABLE_BUBBLE) != 0; 11334 } 11335 11336 /** 11337 * Indicates whether the bubble is currently visually suppressed from the bubble stack. 11338 * 11339 * @see BubbleMetadata.Builder#setSuppressableBubble(boolean) 11340 */ isBubbleSuppressed()11341 public boolean isBubbleSuppressed() { 11342 return (mFlags & FLAG_SUPPRESS_BUBBLE) != 0; 11343 } 11344 11345 /** 11346 * Sets whether the notification associated with the bubble is being visually 11347 * suppressed from the notification shade. When <code>true</code> the notification is 11348 * hidden, when <code>false</code> the notification shows as normal. 11349 * 11350 * @hide 11351 */ setSuppressNotification(boolean suppressed)11352 public void setSuppressNotification(boolean suppressed) { 11353 if (suppressed) { 11354 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 11355 } else { 11356 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 11357 } 11358 } 11359 11360 /** 11361 * Sets whether the bubble should be visually suppressed from the bubble stack if the 11362 * user is viewing the same content outside of the bubble. For example, the user has a 11363 * bubble with Alice and then opens up the main app and navigates to Alice's page. 11364 * 11365 * @hide 11366 */ setSuppressBubble(boolean suppressed)11367 public void setSuppressBubble(boolean suppressed) { 11368 if (suppressed) { 11369 mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 11370 } else { 11371 mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; 11372 } 11373 } 11374 11375 /** 11376 * @hide 11377 */ setFlags(int flags)11378 public void setFlags(int flags) { 11379 mFlags = flags; 11380 } 11381 11382 /** 11383 * @hide 11384 */ getFlags()11385 public int getFlags() { 11386 return mFlags; 11387 } 11388 11389 public static final @android.annotation.NonNull Parcelable.Creator<BubbleMetadata> CREATOR = 11390 new Parcelable.Creator<BubbleMetadata>() { 11391 11392 @Override 11393 public BubbleMetadata createFromParcel(Parcel source) { 11394 return new BubbleMetadata(source); 11395 } 11396 11397 @Override 11398 public BubbleMetadata[] newArray(int size) { 11399 return new BubbleMetadata[size]; 11400 } 11401 }; 11402 11403 @Override describeContents()11404 public int describeContents() { 11405 return 0; 11406 } 11407 11408 @Override writeToParcel(Parcel out, int flags)11409 public void writeToParcel(Parcel out, int flags) { 11410 out.writeInt(mPendingIntent != null ? 1 : 0); 11411 if (mPendingIntent != null) { 11412 mPendingIntent.writeToParcel(out, 0); 11413 } 11414 out.writeInt(mIcon != null ? 1 : 0); 11415 if (mIcon != null) { 11416 mIcon.writeToParcel(out, 0); 11417 } 11418 out.writeInt(mDesiredHeight); 11419 out.writeInt(mFlags); 11420 out.writeInt(mDeleteIntent != null ? 1 : 0); 11421 if (mDeleteIntent != null) { 11422 mDeleteIntent.writeToParcel(out, 0); 11423 } 11424 out.writeInt(mDesiredHeightResId); 11425 out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); 11426 if (!TextUtils.isEmpty(mShortcutId)) { 11427 out.writeString8(mShortcutId); 11428 } 11429 } 11430 11431 /** 11432 * Builder to construct a {@link BubbleMetadata} object. 11433 */ 11434 public static final class Builder { 11435 11436 private PendingIntent mPendingIntent; 11437 private Icon mIcon; 11438 private int mDesiredHeight; 11439 @DimenRes private int mDesiredHeightResId; 11440 private int mFlags; 11441 private PendingIntent mDeleteIntent; 11442 private String mShortcutId; 11443 11444 /** 11445 * @deprecated use {@link Builder#Builder(String)} for a bubble created via a 11446 * {@link ShortcutInfo} or {@link Builder#Builder(PendingIntent, Icon)} for a bubble 11447 * created via a {@link PendingIntent}. 11448 */ 11449 @Deprecated Builder()11450 public Builder() { 11451 } 11452 11453 /** 11454 * Creates a {@link BubbleMetadata.Builder} based on a {@link ShortcutInfo}. To create 11455 * a shortcut bubble, ensure that the shortcut associated with the provided 11456 * {@param shortcutId} is published as a dynamic shortcut that was built with 11457 * {@link ShortcutInfo.Builder#setLongLived(boolean)} being true, otherwise your 11458 * notification will not be able to bubble. 11459 * 11460 * <p>The shortcut icon will be used to represent the bubble when it is collapsed.</p> 11461 * 11462 * <p>The shortcut activity will be used when the bubble is expanded. This will display 11463 * the shortcut activity in a floating window over the existing foreground activity.</p> 11464 * 11465 * <p>When the activity is launched from a bubble, 11466 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 11467 * </p> 11468 * 11469 * <p>If the shortcut has not been published when the bubble notification is sent, 11470 * no bubble will be produced. If the shortcut is deleted while the bubble is active, 11471 * the bubble will be removed.</p> 11472 * 11473 * @throws NullPointerException if shortcutId is null. 11474 * 11475 * @see ShortcutInfo 11476 * @see ShortcutInfo.Builder#setLongLived(boolean) 11477 * @see android.content.pm.ShortcutManager#addDynamicShortcuts(List) 11478 */ Builder(@onNull String shortcutId)11479 public Builder(@NonNull String shortcutId) { 11480 if (TextUtils.isEmpty(shortcutId)) { 11481 throw new NullPointerException("Bubble requires a non-null shortcut id"); 11482 } 11483 mShortcutId = shortcutId; 11484 } 11485 11486 /** 11487 * Creates a {@link BubbleMetadata.Builder} based on the provided intent and icon. 11488 * 11489 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 11490 * should be representative of the content within the bubble. If your app produces 11491 * multiple bubbles, the icon should be unique for each of them.</p> 11492 * 11493 * <p>The intent that will be used when the bubble is expanded. This will display the 11494 * app content in a floating window over the existing foreground activity. The intent 11495 * should point to a resizable activity. </p> 11496 * 11497 * <p>When the activity is launched from a bubble, 11498 * {@link Activity#isLaunchedFromBubble()} will return with {@code true}. 11499 * </p> 11500 * 11501 * Note that the pending intent used here requires PendingIntent.FLAG_MUTABLE. 11502 * 11503 * @throws NullPointerException if intent is null. 11504 * @throws NullPointerException if icon is null. 11505 */ Builder(@onNull PendingIntent intent, @NonNull Icon icon)11506 public Builder(@NonNull PendingIntent intent, @NonNull Icon icon) { 11507 if (intent == null) { 11508 throw new NullPointerException("Bubble requires non-null pending intent"); 11509 } 11510 if (icon == null) { 11511 throw new NullPointerException("Bubbles require non-null icon"); 11512 } 11513 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 11514 && icon.getType() != TYPE_URI) { 11515 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 11516 + "TYPE_URI_ADAPTIVE_BITMAP. " 11517 + "In the future, using an icon of this type will be required."); 11518 } 11519 mPendingIntent = intent; 11520 mIcon = icon; 11521 } 11522 11523 /** 11524 * Sets the intent for the bubble. 11525 * 11526 * <p>The intent that will be used when the bubble is expanded. This will display the 11527 * app content in a floating window over the existing foreground activity. The intent 11528 * should point to a resizable activity. </p> 11529 * 11530 * @throws NullPointerException if intent is null. 11531 * @throws IllegalStateException if this builder was created via 11532 * {@link Builder#Builder(String)}. 11533 */ 11534 @NonNull setIntent(@onNull PendingIntent intent)11535 public BubbleMetadata.Builder setIntent(@NonNull PendingIntent intent) { 11536 if (mShortcutId != null) { 11537 throw new IllegalStateException("Created as a shortcut bubble, cannot set a " 11538 + "PendingIntent. Consider using " 11539 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 11540 } 11541 if (intent == null) { 11542 throw new NullPointerException("Bubble requires non-null pending intent"); 11543 } 11544 mPendingIntent = intent; 11545 return this; 11546 } 11547 11548 /** 11549 * Sets the icon for the bubble. Can only be used if the bubble was created 11550 * via {@link Builder#Builder(PendingIntent, Icon)}. 11551 * 11552 * <p>The icon will be used to represent the bubble when it is collapsed. An icon 11553 * should be representative of the content within the bubble. If your app produces 11554 * multiple bubbles, the icon should be unique for each of them.</p> 11555 * 11556 * <p>It is recommended to use an {@link Icon} of type {@link Icon#TYPE_URI} 11557 * or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}</p> 11558 * 11559 * @throws NullPointerException if icon is null. 11560 * @throws IllegalStateException if this builder was created via 11561 * {@link Builder#Builder(String)}. 11562 */ 11563 @NonNull setIcon(@onNull Icon icon)11564 public BubbleMetadata.Builder setIcon(@NonNull Icon icon) { 11565 if (mShortcutId != null) { 11566 throw new IllegalStateException("Created as a shortcut bubble, cannot set an " 11567 + "Icon. Consider using " 11568 + "BubbleMetadata.Builder(PendingIntent,Icon) instead."); 11569 } 11570 if (icon == null) { 11571 throw new NullPointerException("Bubbles require non-null icon"); 11572 } 11573 if (icon.getType() != TYPE_URI_ADAPTIVE_BITMAP 11574 && icon.getType() != TYPE_URI) { 11575 Log.w(TAG, "Bubbles work best with icons of TYPE_URI or " 11576 + "TYPE_URI_ADAPTIVE_BITMAP. " 11577 + "In the future, using an icon of this type will be required."); 11578 } 11579 mIcon = icon; 11580 return this; 11581 } 11582 11583 /** 11584 * Sets the desired height in DPs for the expanded content of the bubble. 11585 * 11586 * <p>This height may not be respected if there is not enough space on the screen or if 11587 * the provided height is too small to be useful.</p> 11588 * 11589 * <p>If {@link #setDesiredHeightResId(int)} was previously called on this builder, the 11590 * previous value set will be cleared after calling this method, and this value will 11591 * be used instead.</p> 11592 * 11593 * <p>A desired height (in DPs or via resID) is optional.</p> 11594 * 11595 * @see #setDesiredHeightResId(int) 11596 */ 11597 @NonNull setDesiredHeight(@imensionunit = DP) int height)11598 public BubbleMetadata.Builder setDesiredHeight(@Dimension(unit = DP) int height) { 11599 mDesiredHeight = Math.max(height, 0); 11600 mDesiredHeightResId = 0; 11601 return this; 11602 } 11603 11604 11605 /** 11606 * Sets the desired height via resId for the expanded content of the bubble. 11607 * 11608 * <p>This height may not be respected if there is not enough space on the screen or if 11609 * the provided height is too small to be useful.</p> 11610 * 11611 * <p>If {@link #setDesiredHeight(int)} was previously called on this builder, the 11612 * previous value set will be cleared after calling this method, and this value will 11613 * be used instead.</p> 11614 * 11615 * <p>A desired height (in DPs or via resID) is optional.</p> 11616 * 11617 * @see #setDesiredHeight(int) 11618 */ 11619 @NonNull setDesiredHeightResId(@imenRes int heightResId)11620 public BubbleMetadata.Builder setDesiredHeightResId(@DimenRes int heightResId) { 11621 mDesiredHeightResId = heightResId; 11622 mDesiredHeight = 0; 11623 return this; 11624 } 11625 11626 /** 11627 * Sets whether the bubble will be posted in its expanded state. 11628 * 11629 * <p>This flag has no effect if the app posting the bubble is not in the foreground. 11630 * The app is considered foreground if it is visible and on the screen, note that 11631 * a foreground service does not qualify. 11632 * </p> 11633 * 11634 * <p>Generally, this flag should only be set if the user has performed an action to 11635 * request or create a bubble.</p> 11636 * 11637 * <p>Setting this flag is optional; it defaults to false.</p> 11638 */ 11639 @NonNull setAutoExpandBubble(boolean shouldExpand)11640 public BubbleMetadata.Builder setAutoExpandBubble(boolean shouldExpand) { 11641 setFlag(FLAG_AUTO_EXPAND_BUBBLE, shouldExpand); 11642 return this; 11643 } 11644 11645 /** 11646 * Sets whether the bubble will be posted <b>without</b> the associated notification in 11647 * the notification shade. 11648 * 11649 * <p>Generally, this flag should only be set if the user has performed an action to 11650 * request or create a bubble, or if the user has seen the content in the notification 11651 * and the notification is no longer relevant.</p> 11652 * 11653 * <p>Setting this flag is optional; it defaults to false.</p> 11654 */ 11655 @NonNull setSuppressNotification(boolean shouldSuppressNotif)11656 public BubbleMetadata.Builder setSuppressNotification(boolean shouldSuppressNotif) { 11657 setFlag(FLAG_SUPPRESS_NOTIFICATION, shouldSuppressNotif); 11658 return this; 11659 } 11660 11661 /** 11662 * Indicates whether the bubble should be visually suppressed from the bubble stack if 11663 * the user is viewing the same content outside of the bubble. For example, the user has 11664 * a bubble with Alice and then opens up the main app and navigates to Alice's page. 11665 * 11666 * To match the activity and the bubble notification, the bubble notification should 11667 * have a locus id set that matches a locus id set on the activity. 11668 * 11669 * {@link Notification.Builder#setLocusId(LocusId)} 11670 * {@link Activity#setLocusContext(LocusId, Bundle)} 11671 */ 11672 @NonNull setSuppressableBubble(boolean suppressBubble)11673 public BubbleMetadata.Builder setSuppressableBubble(boolean suppressBubble) { 11674 setFlag(FLAG_SUPPRESSABLE_BUBBLE, suppressBubble); 11675 return this; 11676 } 11677 11678 /** 11679 * Sets an intent to send when this bubble is explicitly removed by the user. 11680 * 11681 * <p>Setting a delete intent is optional.</p> 11682 */ 11683 @NonNull setDeleteIntent(@ullable PendingIntent deleteIntent)11684 public BubbleMetadata.Builder setDeleteIntent(@Nullable PendingIntent deleteIntent) { 11685 mDeleteIntent = deleteIntent; 11686 return this; 11687 } 11688 11689 /** 11690 * Creates the {@link BubbleMetadata} defined by this builder. 11691 * 11692 * @throws NullPointerException if required elements have not been set. 11693 */ 11694 @NonNull build()11695 public BubbleMetadata build() { 11696 if (mShortcutId == null && mPendingIntent == null) { 11697 throw new NullPointerException( 11698 "Must supply pending intent or shortcut to bubble"); 11699 } 11700 if (mShortcutId == null && mIcon == null) { 11701 throw new NullPointerException( 11702 "Must supply an icon or shortcut for the bubble"); 11703 } 11704 BubbleMetadata data = new BubbleMetadata(mPendingIntent, mDeleteIntent, 11705 mIcon, mDesiredHeight, mDesiredHeightResId, mShortcutId); 11706 data.setFlags(mFlags); 11707 return data; 11708 } 11709 11710 /** 11711 * @hide 11712 */ setFlag(int mask, boolean value)11713 public BubbleMetadata.Builder setFlag(int mask, boolean value) { 11714 if (value) { 11715 mFlags |= mask; 11716 } else { 11717 mFlags &= ~mask; 11718 } 11719 return this; 11720 } 11721 } 11722 } 11723 11724 11725 // When adding a new Style subclass here, don't forget to update 11726 // Builder.getNotificationStyleClass. 11727 11728 /** 11729 * Extender interface for use with {@link Builder#extend}. Extenders may be used to add 11730 * metadata or change options on a notification builder. 11731 */ 11732 public interface Extender { 11733 /** 11734 * Apply this extender to a notification builder. 11735 * @param builder the builder to be modified. 11736 * @return the build object for chaining. 11737 */ extend(Builder builder)11738 public Builder extend(Builder builder); 11739 } 11740 11741 /** 11742 * Helper class to add wearable extensions to notifications. 11743 * <p class="note"> See 11744 * <a href="{@docRoot}wear/notifications/creating.html">Creating Notifications 11745 * for Android Wear</a> for more information on how to use this class. 11746 * <p> 11747 * To create a notification with wearable extensions: 11748 * <ol> 11749 * <li>Create a {@link android.app.Notification.Builder}, setting any desired 11750 * properties. 11751 * <li>Create a {@link android.app.Notification.WearableExtender}. 11752 * <li>Set wearable-specific properties using the 11753 * {@code add} and {@code set} methods of {@link android.app.Notification.WearableExtender}. 11754 * <li>Call {@link android.app.Notification.Builder#extend} to apply the extensions to a 11755 * notification. 11756 * <li>Post the notification to the notification system with the 11757 * {@code NotificationManager.notify(...)} methods. 11758 * </ol> 11759 * 11760 * <pre class="prettyprint"> 11761 * Notification notif = new Notification.Builder(mContext) 11762 * .setContentTitle("New mail from " + sender.toString()) 11763 * .setContentText(subject) 11764 * .setSmallIcon(R.drawable.new_mail) 11765 * .extend(new Notification.WearableExtender() 11766 * .setContentIcon(R.drawable.new_mail)) 11767 * .build(); 11768 * NotificationManager notificationManger = 11769 * (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 11770 * notificationManger.notify(0, notif);</pre> 11771 * 11772 * <p>Wearable extensions can be accessed on an existing notification by using the 11773 * {@code WearableExtender(Notification)} constructor, 11774 * and then using the {@code get} methods to access values. 11775 * 11776 * <pre class="prettyprint"> 11777 * Notification.WearableExtender wearableExtender = new Notification.WearableExtender( 11778 * notification); 11779 * List<Notification> pages = wearableExtender.getPages();</pre> 11780 */ 11781 public static final class WearableExtender implements Extender { 11782 /** 11783 * Sentinel value for an action index that is unset. 11784 */ 11785 public static final int UNSET_ACTION_INDEX = -1; 11786 11787 /** 11788 * Size value for use with {@link #setCustomSizePreset} to show this notification with 11789 * default sizing. 11790 * <p>For custom display notifications created using {@link #setDisplayIntent}, 11791 * the default is {@link #SIZE_MEDIUM}. All other notifications size automatically based 11792 * on their content. 11793 * 11794 * @deprecated Display intents are no longer supported. 11795 */ 11796 @Deprecated 11797 public static final int SIZE_DEFAULT = 0; 11798 11799 /** 11800 * Size value for use with {@link #setCustomSizePreset} to show this notification 11801 * with an extra small size. 11802 * <p>This value is only applicable for custom display notifications created using 11803 * {@link #setDisplayIntent}. 11804 * 11805 * @deprecated Display intents are no longer supported. 11806 */ 11807 @Deprecated 11808 public static final int SIZE_XSMALL = 1; 11809 11810 /** 11811 * Size value for use with {@link #setCustomSizePreset} to show this notification 11812 * with a small size. 11813 * <p>This value is only applicable for custom display notifications created using 11814 * {@link #setDisplayIntent}. 11815 * 11816 * @deprecated Display intents are no longer supported. 11817 */ 11818 @Deprecated 11819 public static final int SIZE_SMALL = 2; 11820 11821 /** 11822 * Size value for use with {@link #setCustomSizePreset} to show this notification 11823 * with a medium size. 11824 * <p>This value is only applicable for custom display notifications created using 11825 * {@link #setDisplayIntent}. 11826 * 11827 * @deprecated Display intents are no longer supported. 11828 */ 11829 @Deprecated 11830 public static final int SIZE_MEDIUM = 3; 11831 11832 /** 11833 * Size value for use with {@link #setCustomSizePreset} to show this notification 11834 * with a large size. 11835 * <p>This value is only applicable for custom display notifications created using 11836 * {@link #setDisplayIntent}. 11837 * 11838 * @deprecated Display intents are no longer supported. 11839 */ 11840 @Deprecated 11841 public static final int SIZE_LARGE = 4; 11842 11843 /** 11844 * Size value for use with {@link #setCustomSizePreset} to show this notification 11845 * full screen. 11846 * <p>This value is only applicable for custom display notifications created using 11847 * {@link #setDisplayIntent}. 11848 * 11849 * @deprecated Display intents are no longer supported. 11850 */ 11851 @Deprecated 11852 public static final int SIZE_FULL_SCREEN = 5; 11853 11854 /** 11855 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on for a 11856 * short amount of time when this notification is displayed on the screen. This 11857 * is the default value. 11858 * 11859 * @deprecated This feature is no longer supported. 11860 */ 11861 @Deprecated 11862 public static final int SCREEN_TIMEOUT_SHORT = 0; 11863 11864 /** 11865 * Sentinel value for use with {@link #setHintScreenTimeout} to keep the screen on 11866 * for a longer amount of time when this notification is displayed on the screen. 11867 * 11868 * @deprecated This feature is no longer supported. 11869 */ 11870 @Deprecated 11871 public static final int SCREEN_TIMEOUT_LONG = -1; 11872 11873 /** Notification extra which contains wearable extensions */ 11874 private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS"; 11875 11876 // Keys within EXTRA_WEARABLE_EXTENSIONS for wearable options. 11877 private static final String KEY_ACTIONS = "actions"; 11878 private static final String KEY_FLAGS = "flags"; 11879 static final String KEY_DISPLAY_INTENT = "displayIntent"; 11880 private static final String KEY_PAGES = "pages"; 11881 static final String KEY_BACKGROUND = "background"; 11882 private static final String KEY_CONTENT_ICON = "contentIcon"; 11883 private static final String KEY_CONTENT_ICON_GRAVITY = "contentIconGravity"; 11884 private static final String KEY_CONTENT_ACTION_INDEX = "contentActionIndex"; 11885 private static final String KEY_CUSTOM_SIZE_PRESET = "customSizePreset"; 11886 private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight"; 11887 private static final String KEY_GRAVITY = "gravity"; 11888 private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout"; 11889 private static final String KEY_DISMISSAL_ID = "dismissalId"; 11890 private static final String KEY_BRIDGE_TAG = "bridgeTag"; 11891 11892 // Flags bitwise-ored to mFlags 11893 private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1; 11894 private static final int FLAG_HINT_HIDE_ICON = 1 << 1; 11895 private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2; 11896 private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3; 11897 private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4; 11898 private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5; 11899 private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6; 11900 11901 // Default value for flags integer 11902 private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE; 11903 11904 private static final int DEFAULT_CONTENT_ICON_GRAVITY = Gravity.END; 11905 private static final int DEFAULT_GRAVITY = Gravity.BOTTOM; 11906 11907 private ArrayList<Action> mActions = new ArrayList<Action>(); 11908 private int mFlags = DEFAULT_FLAGS; 11909 private PendingIntent mDisplayIntent; 11910 private ArrayList<Notification> mPages = new ArrayList<Notification>(); 11911 private Bitmap mBackground; 11912 private int mContentIcon; 11913 private int mContentIconGravity = DEFAULT_CONTENT_ICON_GRAVITY; 11914 private int mContentActionIndex = UNSET_ACTION_INDEX; 11915 private int mCustomSizePreset = SIZE_DEFAULT; 11916 private int mCustomContentHeight; 11917 private int mGravity = DEFAULT_GRAVITY; 11918 private int mHintScreenTimeout; 11919 private String mDismissalId; 11920 private String mBridgeTag; 11921 11922 /** 11923 * Create a {@link android.app.Notification.WearableExtender} with default 11924 * options. 11925 */ WearableExtender()11926 public WearableExtender() { 11927 } 11928 WearableExtender(Notification notif)11929 public WearableExtender(Notification notif) { 11930 Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS); 11931 if (wearableBundle != null) { 11932 List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class); 11933 if (actions != null) { 11934 mActions.addAll(actions); 11935 } 11936 11937 mFlags = wearableBundle.getInt(KEY_FLAGS, DEFAULT_FLAGS); 11938 mDisplayIntent = wearableBundle.getParcelable( 11939 KEY_DISPLAY_INTENT, PendingIntent.class); 11940 11941 Notification[] pages = getParcelableArrayFromBundle( 11942 wearableBundle, KEY_PAGES, Notification.class); 11943 if (pages != null) { 11944 Collections.addAll(mPages, pages); 11945 } 11946 11947 mBackground = wearableBundle.getParcelable(KEY_BACKGROUND, Bitmap.class); 11948 mContentIcon = wearableBundle.getInt(KEY_CONTENT_ICON); 11949 mContentIconGravity = wearableBundle.getInt(KEY_CONTENT_ICON_GRAVITY, 11950 DEFAULT_CONTENT_ICON_GRAVITY); 11951 mContentActionIndex = wearableBundle.getInt(KEY_CONTENT_ACTION_INDEX, 11952 UNSET_ACTION_INDEX); 11953 mCustomSizePreset = wearableBundle.getInt(KEY_CUSTOM_SIZE_PRESET, 11954 SIZE_DEFAULT); 11955 mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT); 11956 mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY); 11957 mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT); 11958 mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID); 11959 mBridgeTag = wearableBundle.getString(KEY_BRIDGE_TAG); 11960 } 11961 } 11962 11963 /** 11964 * Apply wearable extensions to a notification that is being built. This is typically 11965 * called by the {@link android.app.Notification.Builder#extend} method of 11966 * {@link android.app.Notification.Builder}. 11967 */ 11968 @Override extend(Notification.Builder builder)11969 public Notification.Builder extend(Notification.Builder builder) { 11970 Bundle wearableBundle = new Bundle(); 11971 11972 if (!mActions.isEmpty()) { 11973 wearableBundle.putParcelableArrayList(KEY_ACTIONS, mActions); 11974 } 11975 if (mFlags != DEFAULT_FLAGS) { 11976 wearableBundle.putInt(KEY_FLAGS, mFlags); 11977 } 11978 if (mDisplayIntent != null) { 11979 wearableBundle.putParcelable(KEY_DISPLAY_INTENT, mDisplayIntent); 11980 } 11981 if (!mPages.isEmpty()) { 11982 wearableBundle.putParcelableArray(KEY_PAGES, mPages.toArray( 11983 new Notification[mPages.size()])); 11984 } 11985 11986 if (mBackground != null) { 11987 // Keeping WearableExtender backgrounds in memory despite them being deprecated has 11988 // added noticeable increase in system server and system ui memory usage. After 11989 // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated 11990 // anymore. 11991 if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) { 11992 Log.d(TAG, "Use of background in WearableExtenders has been deprecated and " 11993 + "will not be populated anymore."); 11994 } else { 11995 wearableBundle.putParcelable(KEY_BACKGROUND, mBackground); 11996 } 11997 } 11998 11999 if (mContentIcon != 0) { 12000 wearableBundle.putInt(KEY_CONTENT_ICON, mContentIcon); 12001 } 12002 if (mContentIconGravity != DEFAULT_CONTENT_ICON_GRAVITY) { 12003 wearableBundle.putInt(KEY_CONTENT_ICON_GRAVITY, mContentIconGravity); 12004 } 12005 if (mContentActionIndex != UNSET_ACTION_INDEX) { 12006 wearableBundle.putInt(KEY_CONTENT_ACTION_INDEX, 12007 mContentActionIndex); 12008 } 12009 if (mCustomSizePreset != SIZE_DEFAULT) { 12010 wearableBundle.putInt(KEY_CUSTOM_SIZE_PRESET, mCustomSizePreset); 12011 } 12012 if (mCustomContentHeight != 0) { 12013 wearableBundle.putInt(KEY_CUSTOM_CONTENT_HEIGHT, mCustomContentHeight); 12014 } 12015 if (mGravity != DEFAULT_GRAVITY) { 12016 wearableBundle.putInt(KEY_GRAVITY, mGravity); 12017 } 12018 if (mHintScreenTimeout != 0) { 12019 wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout); 12020 } 12021 if (mDismissalId != null) { 12022 wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId); 12023 } 12024 if (mBridgeTag != null) { 12025 wearableBundle.putString(KEY_BRIDGE_TAG, mBridgeTag); 12026 } 12027 12028 builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle); 12029 return builder; 12030 } 12031 12032 @Override clone()12033 public WearableExtender clone() { 12034 WearableExtender that = new WearableExtender(); 12035 that.mActions = new ArrayList<Action>(this.mActions); 12036 that.mFlags = this.mFlags; 12037 that.mDisplayIntent = this.mDisplayIntent; 12038 that.mPages = new ArrayList<Notification>(this.mPages); 12039 that.mBackground = this.mBackground; 12040 that.mContentIcon = this.mContentIcon; 12041 that.mContentIconGravity = this.mContentIconGravity; 12042 that.mContentActionIndex = this.mContentActionIndex; 12043 that.mCustomSizePreset = this.mCustomSizePreset; 12044 that.mCustomContentHeight = this.mCustomContentHeight; 12045 that.mGravity = this.mGravity; 12046 that.mHintScreenTimeout = this.mHintScreenTimeout; 12047 that.mDismissalId = this.mDismissalId; 12048 that.mBridgeTag = this.mBridgeTag; 12049 return that; 12050 } 12051 12052 /** 12053 * Add a wearable action to this notification. 12054 * 12055 * <p>When wearable actions are added using this method, the set of actions that 12056 * show on a wearable device splits from devices that only show actions added 12057 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 12058 * of which actions display on different devices. 12059 * 12060 * @param action the action to add to this notification 12061 * @return this object for method chaining 12062 * @see android.app.Notification.Action 12063 */ addAction(Action action)12064 public WearableExtender addAction(Action action) { 12065 mActions.add(action); 12066 return this; 12067 } 12068 12069 /** 12070 * Adds wearable actions to this notification. 12071 * 12072 * <p>When wearable actions are added using this method, the set of actions that 12073 * show on a wearable device splits from devices that only show actions added 12074 * using {@link android.app.Notification.Builder#addAction}. This allows for customization 12075 * of which actions display on different devices. 12076 * 12077 * @param actions the actions to add to this notification 12078 * @return this object for method chaining 12079 * @see android.app.Notification.Action 12080 */ addActions(List<Action> actions)12081 public WearableExtender addActions(List<Action> actions) { 12082 mActions.addAll(actions); 12083 return this; 12084 } 12085 12086 /** 12087 * Clear all wearable actions present on this builder. 12088 * @return this object for method chaining. 12089 * @see #addAction 12090 */ clearActions()12091 public WearableExtender clearActions() { 12092 mActions.clear(); 12093 return this; 12094 } 12095 12096 /** 12097 * Get the wearable actions present on this notification. 12098 */ getActions()12099 public List<Action> getActions() { 12100 return mActions; 12101 } 12102 12103 /** 12104 * Set an intent to launch inside of an activity view when displaying 12105 * this notification. The {@link PendingIntent} provided should be for an activity. 12106 * 12107 * <pre class="prettyprint"> 12108 * Intent displayIntent = new Intent(context, MyDisplayActivity.class); 12109 * PendingIntent displayPendingIntent = PendingIntent.getActivity(context, 12110 * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 12111 * Notification notif = new Notification.Builder(context) 12112 * .extend(new Notification.WearableExtender() 12113 * .setDisplayIntent(displayPendingIntent) 12114 * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM)) 12115 * .build();</pre> 12116 * 12117 * <p>The activity to launch needs to allow embedding, must be exported, and 12118 * should have an empty task affinity. It is also recommended to use the device 12119 * default light theme. 12120 * 12121 * <p>Example AndroidManifest.xml entry: 12122 * <pre class="prettyprint"> 12123 * <activity android:name="com.example.MyDisplayActivity" 12124 * android:exported="true" 12125 * android:allowEmbedded="true" 12126 * android:taskAffinity="" 12127 * android:theme="@android:style/Theme.DeviceDefault.Light" /></pre> 12128 * 12129 * @param intent the {@link PendingIntent} for an activity 12130 * @return this object for method chaining 12131 * @see android.app.Notification.WearableExtender#getDisplayIntent 12132 * @deprecated Display intents are no longer supported. 12133 */ 12134 @Deprecated setDisplayIntent(PendingIntent intent)12135 public WearableExtender setDisplayIntent(PendingIntent intent) { 12136 mDisplayIntent = intent; 12137 return this; 12138 } 12139 12140 /** 12141 * Get the intent to launch inside of an activity view when displaying this 12142 * notification. This {@code PendingIntent} should be for an activity. 12143 * 12144 * @deprecated Display intents are no longer supported. 12145 */ 12146 @Deprecated getDisplayIntent()12147 public PendingIntent getDisplayIntent() { 12148 return mDisplayIntent; 12149 } 12150 12151 /** 12152 * Add an additional page of content to display with this notification. The current 12153 * notification forms the first page, and pages added using this function form 12154 * subsequent pages. This field can be used to separate a notification into multiple 12155 * sections. 12156 * 12157 * @param page the notification to add as another page 12158 * @return this object for method chaining 12159 * @see android.app.Notification.WearableExtender#getPages 12160 * @deprecated Multiple content pages are no longer supported. 12161 */ 12162 @Deprecated addPage(Notification page)12163 public WearableExtender addPage(Notification page) { 12164 mPages.add(page); 12165 return this; 12166 } 12167 12168 /** 12169 * Add additional pages of content to display with this notification. The current 12170 * notification forms the first page, and pages added using this function form 12171 * subsequent pages. This field can be used to separate a notification into multiple 12172 * sections. 12173 * 12174 * @param pages a list of notifications 12175 * @return this object for method chaining 12176 * @see android.app.Notification.WearableExtender#getPages 12177 * @deprecated Multiple content pages are no longer supported. 12178 */ 12179 @Deprecated addPages(List<Notification> pages)12180 public WearableExtender addPages(List<Notification> pages) { 12181 mPages.addAll(pages); 12182 return this; 12183 } 12184 12185 /** 12186 * Clear all additional pages present on this builder. 12187 * @return this object for method chaining. 12188 * @see #addPage 12189 * @deprecated Multiple content pages are no longer supported. 12190 */ 12191 @Deprecated clearPages()12192 public WearableExtender clearPages() { 12193 mPages.clear(); 12194 return this; 12195 } 12196 12197 /** 12198 * Get the array of additional pages of content for displaying this notification. The 12199 * current notification forms the first page, and elements within this array form 12200 * subsequent pages. This field can be used to separate a notification into multiple 12201 * sections. 12202 * @return the pages for this notification 12203 * @deprecated Multiple content pages are no longer supported. 12204 */ 12205 @Deprecated getPages()12206 public List<Notification> getPages() { 12207 return mPages; 12208 } 12209 12210 /** 12211 * Set a background image to be displayed behind the notification content. 12212 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 12213 * will work with any notification style. 12214 * 12215 * @param background the background bitmap 12216 * @return this object for method chaining 12217 * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. 12218 * The wearable background is not used by wearables anymore and uses up 12219 * unnecessary memory. 12220 */ 12221 @Deprecated setBackground(Bitmap background)12222 public WearableExtender setBackground(Bitmap background) { 12223 // Keeping WearableExtender backgrounds in memory despite them being deprecated has 12224 // added noticeable increase in system server and system ui memory usage. After 12225 // target VERSION_CODE#VANILLA_ICE_CREAM the background will not be populated anymore. 12226 if (CompatChanges.isChangeEnabled(WEARABLE_EXTENDER_BACKGROUND_BLOCKED)) { 12227 Log.d(TAG, "Use of background in WearableExtenders has been deprecated and " 12228 + "will not be populated anymore."); 12229 } else { 12230 mBackground = background; 12231 } 12232 return this; 12233 } 12234 12235 /** 12236 * Get a background image to be displayed behind the notification content. 12237 * Contrary to the {@link android.app.Notification.BigPictureStyle}, this background 12238 * will work with any notification style. 12239 * 12240 * @return the background image 12241 * @removed Not functional since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. The 12242 * wearable background is not used by wearables anymore and uses up 12243 * unnecessary memory. 12244 */ 12245 @Deprecated getBackground()12246 public Bitmap getBackground() { 12247 Log.w(TAG, "Use of background in WearableExtender has been removed, returning null."); 12248 return mBackground; 12249 } 12250 12251 /** 12252 * Set an icon that goes with the content of this notification. 12253 */ 12254 @Deprecated setContentIcon(int icon)12255 public WearableExtender setContentIcon(int icon) { 12256 mContentIcon = icon; 12257 return this; 12258 } 12259 12260 /** 12261 * Get an icon that goes with the content of this notification. 12262 */ 12263 @Deprecated getContentIcon()12264 public int getContentIcon() { 12265 return mContentIcon; 12266 } 12267 12268 /** 12269 * Set the gravity that the content icon should have within the notification display. 12270 * Supported values include {@link android.view.Gravity#START} and 12271 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 12272 * @see #setContentIcon 12273 */ 12274 @Deprecated setContentIconGravity(int contentIconGravity)12275 public WearableExtender setContentIconGravity(int contentIconGravity) { 12276 mContentIconGravity = contentIconGravity; 12277 return this; 12278 } 12279 12280 /** 12281 * Get the gravity that the content icon should have within the notification display. 12282 * Supported values include {@link android.view.Gravity#START} and 12283 * {@link android.view.Gravity#END}. The default value is {@link android.view.Gravity#END}. 12284 * @see #getContentIcon 12285 */ 12286 @Deprecated getContentIconGravity()12287 public int getContentIconGravity() { 12288 return mContentIconGravity; 12289 } 12290 12291 /** 12292 * Set an action from this notification's actions as the primary action. If the action has a 12293 * {@link RemoteInput} associated with it, shortcuts to the options for that input are shown 12294 * directly on the notification. 12295 * 12296 * @param actionIndex The index of the primary action. 12297 * If wearable actions were added to the main notification, this index 12298 * will apply to that list, otherwise it will apply to the regular 12299 * actions list. 12300 */ setContentAction(int actionIndex)12301 public WearableExtender setContentAction(int actionIndex) { 12302 mContentActionIndex = actionIndex; 12303 return this; 12304 } 12305 12306 /** 12307 * Get the index of the notification action, if any, that was specified as the primary 12308 * action. 12309 * 12310 * <p>If wearable specific actions were added to the main notification, this index will 12311 * apply to that list, otherwise it will apply to the regular actions list. 12312 * 12313 * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected. 12314 */ getContentAction()12315 public int getContentAction() { 12316 return mContentActionIndex; 12317 } 12318 12319 /** 12320 * Set the gravity that this notification should have within the available viewport space. 12321 * Supported values include {@link android.view.Gravity#TOP}, 12322 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 12323 * The default value is {@link android.view.Gravity#BOTTOM}. 12324 */ 12325 @Deprecated setGravity(int gravity)12326 public WearableExtender setGravity(int gravity) { 12327 mGravity = gravity; 12328 return this; 12329 } 12330 12331 /** 12332 * Get the gravity that this notification should have within the available viewport space. 12333 * Supported values include {@link android.view.Gravity#TOP}, 12334 * {@link android.view.Gravity#CENTER_VERTICAL} and {@link android.view.Gravity#BOTTOM}. 12335 * The default value is {@link android.view.Gravity#BOTTOM}. 12336 */ 12337 @Deprecated getGravity()12338 public int getGravity() { 12339 return mGravity; 12340 } 12341 12342 /** 12343 * Set the custom size preset for the display of this notification out of the available 12344 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 12345 * {@link #SIZE_LARGE}. 12346 * <p>Some custom size presets are only applicable for custom display notifications created 12347 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. Check the 12348 * documentation for the preset in question. See also 12349 * {@link #setCustomContentHeight} and {@link #getCustomSizePreset}. 12350 */ 12351 @Deprecated setCustomSizePreset(int sizePreset)12352 public WearableExtender setCustomSizePreset(int sizePreset) { 12353 mCustomSizePreset = sizePreset; 12354 return this; 12355 } 12356 12357 /** 12358 * Get the custom size preset for the display of this notification out of the available 12359 * presets found in {@link android.app.Notification.WearableExtender}, e.g. 12360 * {@link #SIZE_LARGE}. 12361 * <p>Some custom size presets are only applicable for custom display notifications created 12362 * using {@link #setDisplayIntent}. Check the documentation for the preset in question. 12363 * See also {@link #setCustomContentHeight} and {@link #setCustomSizePreset}. 12364 */ 12365 @Deprecated getCustomSizePreset()12366 public int getCustomSizePreset() { 12367 return mCustomSizePreset; 12368 } 12369 12370 /** 12371 * Set the custom height in pixels for the display of this notification's content. 12372 * <p>This option is only available for custom display notifications created 12373 * using {@link android.app.Notification.WearableExtender#setDisplayIntent}. See also 12374 * {@link android.app.Notification.WearableExtender#setCustomSizePreset} and 12375 * {@link #getCustomContentHeight}. 12376 */ 12377 @Deprecated setCustomContentHeight(int height)12378 public WearableExtender setCustomContentHeight(int height) { 12379 mCustomContentHeight = height; 12380 return this; 12381 } 12382 12383 /** 12384 * Get the custom height in pixels for the display of this notification's content. 12385 * <p>This option is only available for custom display notifications created 12386 * using {@link #setDisplayIntent}. See also {@link #setCustomSizePreset} and 12387 * {@link #setCustomContentHeight}. 12388 */ 12389 @Deprecated getCustomContentHeight()12390 public int getCustomContentHeight() { 12391 return mCustomContentHeight; 12392 } 12393 12394 /** 12395 * Set whether the scrolling position for the contents of this notification should start 12396 * at the bottom of the contents instead of the top when the contents are too long to 12397 * display within the screen. Default is false (start scroll at the top). 12398 */ setStartScrollBottom(boolean startScrollBottom)12399 public WearableExtender setStartScrollBottom(boolean startScrollBottom) { 12400 setFlag(FLAG_START_SCROLL_BOTTOM, startScrollBottom); 12401 return this; 12402 } 12403 12404 /** 12405 * Get whether the scrolling position for the contents of this notification should start 12406 * at the bottom of the contents instead of the top when the contents are too long to 12407 * display within the screen. Default is false (start scroll at the top). 12408 */ getStartScrollBottom()12409 public boolean getStartScrollBottom() { 12410 return (mFlags & FLAG_START_SCROLL_BOTTOM) != 0; 12411 } 12412 12413 /** 12414 * Set whether the content intent is available when the wearable device is not connected 12415 * to a companion device. The user can still trigger this intent when the wearable device 12416 * is offline, but a visual hint will indicate that the content intent may not be available. 12417 * Defaults to true. 12418 */ setContentIntentAvailableOffline( boolean contentIntentAvailableOffline)12419 public WearableExtender setContentIntentAvailableOffline( 12420 boolean contentIntentAvailableOffline) { 12421 setFlag(FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE, contentIntentAvailableOffline); 12422 return this; 12423 } 12424 12425 /** 12426 * Get whether the content intent is available when the wearable device is not connected 12427 * to a companion device. The user can still trigger this intent when the wearable device 12428 * is offline, but a visual hint will indicate that the content intent may not be available. 12429 * Defaults to true. 12430 */ getContentIntentAvailableOffline()12431 public boolean getContentIntentAvailableOffline() { 12432 return (mFlags & FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE) != 0; 12433 } 12434 12435 /** 12436 * Set a hint that this notification's icon should not be displayed. 12437 * @param hintHideIcon {@code true} to hide the icon, {@code false} otherwise. 12438 * @return this object for method chaining 12439 */ 12440 @Deprecated setHintHideIcon(boolean hintHideIcon)12441 public WearableExtender setHintHideIcon(boolean hintHideIcon) { 12442 setFlag(FLAG_HINT_HIDE_ICON, hintHideIcon); 12443 return this; 12444 } 12445 12446 /** 12447 * Get a hint that this notification's icon should not be displayed. 12448 * @return {@code true} if this icon should not be displayed, false otherwise. 12449 * The default value is {@code false} if this was never set. 12450 */ 12451 @Deprecated getHintHideIcon()12452 public boolean getHintHideIcon() { 12453 return (mFlags & FLAG_HINT_HIDE_ICON) != 0; 12454 } 12455 12456 /** 12457 * Set a visual hint that only the background image of this notification should be 12458 * displayed, and other semantic content should be hidden. This hint is only applicable 12459 * to sub-pages added using {@link #addPage}. 12460 */ 12461 @Deprecated setHintShowBackgroundOnly(boolean hintShowBackgroundOnly)12462 public WearableExtender setHintShowBackgroundOnly(boolean hintShowBackgroundOnly) { 12463 setFlag(FLAG_HINT_SHOW_BACKGROUND_ONLY, hintShowBackgroundOnly); 12464 return this; 12465 } 12466 12467 /** 12468 * Get a visual hint that only the background image of this notification should be 12469 * displayed, and other semantic content should be hidden. This hint is only applicable 12470 * to sub-pages added using {@link android.app.Notification.WearableExtender#addPage}. 12471 */ 12472 @Deprecated getHintShowBackgroundOnly()12473 public boolean getHintShowBackgroundOnly() { 12474 return (mFlags & FLAG_HINT_SHOW_BACKGROUND_ONLY) != 0; 12475 } 12476 12477 /** 12478 * Set a hint that this notification's background should not be clipped if possible, 12479 * and should instead be resized to fully display on the screen, retaining the aspect 12480 * ratio of the image. This can be useful for images like barcodes or qr codes. 12481 * @param hintAvoidBackgroundClipping {@code true} to avoid clipping if possible. 12482 * @return this object for method chaining 12483 */ 12484 @Deprecated setHintAvoidBackgroundClipping( boolean hintAvoidBackgroundClipping)12485 public WearableExtender setHintAvoidBackgroundClipping( 12486 boolean hintAvoidBackgroundClipping) { 12487 setFlag(FLAG_HINT_AVOID_BACKGROUND_CLIPPING, hintAvoidBackgroundClipping); 12488 return this; 12489 } 12490 12491 /** 12492 * Get a hint that this notification's background should not be clipped if possible, 12493 * and should instead be resized to fully display on the screen, retaining the aspect 12494 * ratio of the image. This can be useful for images like barcodes or qr codes. 12495 * @return {@code true} if it's ok if the background is clipped on the screen, false 12496 * otherwise. The default value is {@code false} if this was never set. 12497 */ 12498 @Deprecated getHintAvoidBackgroundClipping()12499 public boolean getHintAvoidBackgroundClipping() { 12500 return (mFlags & FLAG_HINT_AVOID_BACKGROUND_CLIPPING) != 0; 12501 } 12502 12503 /** 12504 * Set a hint that the screen should remain on for at least this duration when 12505 * this notification is displayed on the screen. 12506 * @param timeout The requested screen timeout in milliseconds. Can also be either 12507 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 12508 * @return this object for method chaining 12509 */ 12510 @Deprecated setHintScreenTimeout(int timeout)12511 public WearableExtender setHintScreenTimeout(int timeout) { 12512 mHintScreenTimeout = timeout; 12513 return this; 12514 } 12515 12516 /** 12517 * Get the duration, in milliseconds, that the screen should remain on for 12518 * when this notification is displayed. 12519 * @return the duration in milliseconds if > 0, or either one of the sentinel values 12520 * {@link #SCREEN_TIMEOUT_SHORT} or {@link #SCREEN_TIMEOUT_LONG}. 12521 */ 12522 @Deprecated getHintScreenTimeout()12523 public int getHintScreenTimeout() { 12524 return mHintScreenTimeout; 12525 } 12526 12527 /** 12528 * Set a hint that this notification's {@link BigPictureStyle} (if present) should be 12529 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 12530 * qr codes, as well as other simple black-and-white tickets. 12531 * @param hintAmbientBigPicture {@code true} to enable converstion and ambient. 12532 * @return this object for method chaining 12533 * @deprecated This feature is no longer supported. 12534 */ 12535 @Deprecated setHintAmbientBigPicture(boolean hintAmbientBigPicture)12536 public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) { 12537 setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture); 12538 return this; 12539 } 12540 12541 /** 12542 * Get a hint that this notification's {@link BigPictureStyle} (if present) should be 12543 * converted to low-bit and displayed in ambient mode, especially useful for barcodes and 12544 * qr codes, as well as other simple black-and-white tickets. 12545 * @return {@code true} if it should be displayed in ambient, false otherwise 12546 * otherwise. The default value is {@code false} if this was never set. 12547 * @deprecated This feature is no longer supported. 12548 */ 12549 @Deprecated getHintAmbientBigPicture()12550 public boolean getHintAmbientBigPicture() { 12551 return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0; 12552 } 12553 12554 /** 12555 * Set a hint that this notification's content intent will launch an {@link Activity} 12556 * directly, telling the platform that it can generate the appropriate transitions. 12557 * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch 12558 * an activity and transitions should be generated, false otherwise. 12559 * @return this object for method chaining 12560 */ setHintContentIntentLaunchesActivity( boolean hintContentIntentLaunchesActivity)12561 public WearableExtender setHintContentIntentLaunchesActivity( 12562 boolean hintContentIntentLaunchesActivity) { 12563 setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity); 12564 return this; 12565 } 12566 12567 /** 12568 * Get a hint that this notification's content intent will launch an {@link Activity} 12569 * directly, telling the platform that it can generate the appropriate transitions 12570 * @return {@code true} if the content intent will launch an activity and transitions should 12571 * be generated, false otherwise. The default value is {@code false} if this was never set. 12572 */ getHintContentIntentLaunchesActivity()12573 public boolean getHintContentIntentLaunchesActivity() { 12574 return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0; 12575 } 12576 12577 /** 12578 * Sets the dismissal id for this notification. If a notification is posted with a 12579 * dismissal id, then when that notification is canceled, notifications on other wearables 12580 * and the paired Android phone having that same dismissal id will also be canceled. See 12581 * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to 12582 * Notifications</a> for more information. 12583 * @param dismissalId the dismissal id of the notification. 12584 * @return this object for method chaining 12585 */ setDismissalId(String dismissalId)12586 public WearableExtender setDismissalId(String dismissalId) { 12587 mDismissalId = dismissalId; 12588 return this; 12589 } 12590 12591 /** 12592 * Returns the dismissal id of the notification. 12593 * @return the dismissal id of the notification or null if it has not been set. 12594 */ getDismissalId()12595 public String getDismissalId() { 12596 return mDismissalId; 12597 } 12598 12599 /** 12600 * Sets a bridge tag for this notification. A bridge tag can be set for notifications 12601 * posted from a phone to provide finer-grained control on what notifications are bridged 12602 * to wearables. See <a href="{@docRoot}wear/notifications/index.html">Adding Wearable 12603 * Features to Notifications</a> for more information. 12604 * @param bridgeTag the bridge tag of the notification. 12605 * @return this object for method chaining 12606 */ setBridgeTag(String bridgeTag)12607 public WearableExtender setBridgeTag(String bridgeTag) { 12608 mBridgeTag = bridgeTag; 12609 return this; 12610 } 12611 12612 /** 12613 * Returns the bridge tag of the notification. 12614 * @return the bridge tag or null if not present. 12615 */ getBridgeTag()12616 public String getBridgeTag() { 12617 return mBridgeTag; 12618 } 12619 setFlag(int mask, boolean value)12620 private void setFlag(int mask, boolean value) { 12621 if (value) { 12622 mFlags |= mask; 12623 } else { 12624 mFlags &= ~mask; 12625 } 12626 } 12627 visitUris(@onNull Consumer<Uri> visitor)12628 private void visitUris(@NonNull Consumer<Uri> visitor) { 12629 for (Action action : mActions) { 12630 action.visitUris(visitor); 12631 } 12632 } 12633 } 12634 12635 /** 12636 * <p>Helper class to add Android Auto extensions to notifications. To create a notification 12637 * with car extensions: 12638 * 12639 * <ol> 12640 * <li>Create an {@link Notification.Builder}, setting any desired 12641 * properties. 12642 * <li>Create a {@link CarExtender}. 12643 * <li>Set car-specific properties using the {@code add} and {@code set} methods of 12644 * {@link CarExtender}. 12645 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 12646 * to apply the extensions to a notification. 12647 * </ol> 12648 * 12649 * <pre class="prettyprint"> 12650 * Notification notification = new Notification.Builder(context) 12651 * ... 12652 * .extend(new CarExtender() 12653 * .set*(...)) 12654 * .build(); 12655 * </pre> 12656 * 12657 * <p>Car extensions can be accessed on an existing notification by using the 12658 * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods 12659 * to access values. 12660 */ 12661 public static final class CarExtender implements Extender { 12662 private static final String TAG = "CarExtender"; 12663 12664 private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS"; 12665 private static final String EXTRA_LARGE_ICON = "large_icon"; 12666 private static final String EXTRA_CONVERSATION = "car_conversation"; 12667 private static final String EXTRA_COLOR = "app_color"; 12668 12669 private Bitmap mLargeIcon; 12670 private UnreadConversation mUnreadConversation; 12671 private int mColor = Notification.COLOR_DEFAULT; 12672 12673 /** 12674 * Create a {@link CarExtender} with default options. 12675 */ CarExtender()12676 public CarExtender() { 12677 } 12678 12679 /** 12680 * Create a {@link CarExtender} from the CarExtender options of an existing Notification. 12681 * 12682 * @param notif The notification from which to copy options. 12683 */ CarExtender(Notification notif)12684 public CarExtender(Notification notif) { 12685 Bundle carBundle = notif.extras == null ? 12686 null : notif.extras.getBundle(EXTRA_CAR_EXTENDER); 12687 if (carBundle != null) { 12688 mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON, Bitmap.class); 12689 mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT); 12690 12691 Bundle b = carBundle.getBundle(EXTRA_CONVERSATION); 12692 mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b); 12693 } 12694 } 12695 12696 /** 12697 * Apply car extensions to a notification that is being built. This is typically called by 12698 * the {@link Notification.Builder#extend(Notification.Extender)} 12699 * method of {@link Notification.Builder}. 12700 */ 12701 @Override extend(Notification.Builder builder)12702 public Notification.Builder extend(Notification.Builder builder) { 12703 Bundle carExtensions = new Bundle(); 12704 12705 if (mLargeIcon != null) { 12706 carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 12707 } 12708 if (mColor != Notification.COLOR_DEFAULT) { 12709 carExtensions.putInt(EXTRA_COLOR, mColor); 12710 } 12711 12712 if (mUnreadConversation != null) { 12713 Bundle b = mUnreadConversation.getBundleForUnreadConversation(); 12714 carExtensions.putBundle(EXTRA_CONVERSATION, b); 12715 } 12716 12717 builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions); 12718 return builder; 12719 } 12720 12721 /** 12722 * Sets the accent color to use when Android Auto presents the notification. 12723 * 12724 * Android Auto uses the color set with {@link Notification.Builder#setColor(int)} 12725 * to accent the displayed notification. However, not all colors are acceptable in an 12726 * automotive setting. This method can be used to override the color provided in the 12727 * notification in such a situation. 12728 */ setColor(@olorInt int color)12729 public CarExtender setColor(@ColorInt int color) { 12730 mColor = color; 12731 return this; 12732 } 12733 12734 /** 12735 * Gets the accent color. 12736 * 12737 * @see #setColor 12738 */ 12739 @ColorInt getColor()12740 public int getColor() { 12741 return mColor; 12742 } 12743 12744 /** 12745 * Sets the large icon of the car notification. 12746 * 12747 * If no large icon is set in the extender, Android Auto will display the icon 12748 * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)} 12749 * 12750 * @param largeIcon The large icon to use in the car notification. 12751 * @return This object for method chaining. 12752 */ setLargeIcon(Bitmap largeIcon)12753 public CarExtender setLargeIcon(Bitmap largeIcon) { 12754 mLargeIcon = largeIcon; 12755 return this; 12756 } 12757 12758 /** 12759 * Gets the large icon used in this car notification, or null if no icon has been set. 12760 * 12761 * @return The large icon for the car notification. 12762 * @see CarExtender#setLargeIcon 12763 */ getLargeIcon()12764 public Bitmap getLargeIcon() { 12765 return mLargeIcon; 12766 } 12767 12768 /** 12769 * Sets the unread conversation in a message notification. 12770 * 12771 * @param unreadConversation The unread part of the conversation this notification conveys. 12772 * @return This object for method chaining. 12773 */ setUnreadConversation(UnreadConversation unreadConversation)12774 public CarExtender setUnreadConversation(UnreadConversation unreadConversation) { 12775 mUnreadConversation = unreadConversation; 12776 return this; 12777 } 12778 12779 /** 12780 * Returns the unread conversation conveyed by this notification. 12781 * 12782 * @see #setUnreadConversation(UnreadConversation) 12783 */ getUnreadConversation()12784 public UnreadConversation getUnreadConversation() { 12785 return mUnreadConversation; 12786 } 12787 12788 /** 12789 * A class which holds the unread messages from a conversation. 12790 */ 12791 public static class UnreadConversation { 12792 private static final String KEY_AUTHOR = "author"; 12793 private static final String KEY_TEXT = "text"; 12794 private static final String KEY_MESSAGES = "messages"; 12795 static final String KEY_REMOTE_INPUT = "remote_input"; 12796 static final String KEY_ON_REPLY = "on_reply"; 12797 static final String KEY_ON_READ = "on_read"; 12798 private static final String KEY_PARTICIPANTS = "participants"; 12799 private static final String KEY_TIMESTAMP = "timestamp"; 12800 12801 private final String[] mMessages; 12802 private final RemoteInput mRemoteInput; 12803 private final PendingIntent mReplyPendingIntent; 12804 private final PendingIntent mReadPendingIntent; 12805 private final String[] mParticipants; 12806 private final long mLatestTimestamp; 12807 UnreadConversation(String[] messages, RemoteInput remoteInput, PendingIntent replyPendingIntent, PendingIntent readPendingIntent, String[] participants, long latestTimestamp)12808 UnreadConversation(String[] messages, RemoteInput remoteInput, 12809 PendingIntent replyPendingIntent, PendingIntent readPendingIntent, 12810 String[] participants, long latestTimestamp) { 12811 mMessages = messages; 12812 mRemoteInput = remoteInput; 12813 mReadPendingIntent = readPendingIntent; 12814 mReplyPendingIntent = replyPendingIntent; 12815 mParticipants = participants; 12816 mLatestTimestamp = latestTimestamp; 12817 } 12818 12819 /** 12820 * Gets the list of messages conveyed by this notification. 12821 */ getMessages()12822 public String[] getMessages() { 12823 return mMessages; 12824 } 12825 12826 /** 12827 * Gets the remote input that will be used to convey the response to a message list, or 12828 * null if no such remote input exists. 12829 */ getRemoteInput()12830 public RemoteInput getRemoteInput() { 12831 return mRemoteInput; 12832 } 12833 12834 /** 12835 * Gets the pending intent that will be triggered when the user replies to this 12836 * notification. 12837 */ getReplyPendingIntent()12838 public PendingIntent getReplyPendingIntent() { 12839 return mReplyPendingIntent; 12840 } 12841 12842 /** 12843 * Gets the pending intent that Android Auto will send after it reads aloud all messages 12844 * in this object's message list. 12845 */ getReadPendingIntent()12846 public PendingIntent getReadPendingIntent() { 12847 return mReadPendingIntent; 12848 } 12849 12850 /** 12851 * Gets the participants in the conversation. 12852 */ getParticipants()12853 public String[] getParticipants() { 12854 return mParticipants; 12855 } 12856 12857 /** 12858 * Gets the firs participant in the conversation. 12859 */ getParticipant()12860 public String getParticipant() { 12861 return mParticipants.length > 0 ? mParticipants[0] : null; 12862 } 12863 12864 /** 12865 * Gets the timestamp of the conversation. 12866 */ getLatestTimestamp()12867 public long getLatestTimestamp() { 12868 return mLatestTimestamp; 12869 } 12870 getBundleForUnreadConversation()12871 Bundle getBundleForUnreadConversation() { 12872 Bundle b = new Bundle(); 12873 String author = null; 12874 if (mParticipants != null && mParticipants.length > 1) { 12875 author = mParticipants[0]; 12876 } 12877 Parcelable[] messages = new Parcelable[mMessages.length]; 12878 for (int i = 0; i < messages.length; i++) { 12879 Bundle m = new Bundle(); 12880 m.putString(KEY_TEXT, mMessages[i]); 12881 m.putString(KEY_AUTHOR, author); 12882 messages[i] = m; 12883 } 12884 b.putParcelableArray(KEY_MESSAGES, messages); 12885 if (mRemoteInput != null) { 12886 b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput); 12887 } 12888 b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent); 12889 b.putParcelable(KEY_ON_READ, mReadPendingIntent); 12890 b.putStringArray(KEY_PARTICIPANTS, mParticipants); 12891 b.putLong(KEY_TIMESTAMP, mLatestTimestamp); 12892 return b; 12893 } 12894 getUnreadConversationFromBundle(Bundle b)12895 static UnreadConversation getUnreadConversationFromBundle(Bundle b) { 12896 if (b == null) { 12897 return null; 12898 } 12899 Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES, 12900 Parcelable.class); 12901 String[] messages = null; 12902 if (parcelableMessages != null) { 12903 String[] tmp = new String[parcelableMessages.length]; 12904 boolean success = true; 12905 for (int i = 0; i < tmp.length; i++) { 12906 if (!(parcelableMessages[i] instanceof Bundle)) { 12907 success = false; 12908 break; 12909 } 12910 tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT); 12911 if (tmp[i] == null) { 12912 success = false; 12913 break; 12914 } 12915 } 12916 if (success) { 12917 messages = tmp; 12918 } else { 12919 return null; 12920 } 12921 } 12922 12923 PendingIntent onRead = b.getParcelable(KEY_ON_READ, PendingIntent.class); 12924 PendingIntent onReply = b.getParcelable(KEY_ON_REPLY, PendingIntent.class); 12925 12926 RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT, RemoteInput.class); 12927 12928 String[] participants = b.getStringArray(KEY_PARTICIPANTS); 12929 if (participants == null || participants.length != 1) { 12930 return null; 12931 } 12932 12933 return new UnreadConversation(messages, 12934 remoteInput, 12935 onReply, 12936 onRead, 12937 participants, b.getLong(KEY_TIMESTAMP)); 12938 } 12939 }; 12940 12941 /** 12942 * Builder class for {@link CarExtender.UnreadConversation} objects. 12943 */ 12944 public static class Builder { 12945 private final List<String> mMessages = new ArrayList<String>(); 12946 private final String mParticipant; 12947 private RemoteInput mRemoteInput; 12948 private PendingIntent mReadPendingIntent; 12949 private PendingIntent mReplyPendingIntent; 12950 private long mLatestTimestamp; 12951 12952 /** 12953 * Constructs a new builder for {@link CarExtender.UnreadConversation}. 12954 * 12955 * @param name The name of the other participant in the conversation. 12956 */ Builder(String name)12957 public Builder(String name) { 12958 mParticipant = name; 12959 } 12960 12961 /** 12962 * Appends a new unread message to the list of messages for this conversation. 12963 * 12964 * The messages should be added from oldest to newest. 12965 * 12966 * @param message The text of the new unread message. 12967 * @return This object for method chaining. 12968 */ addMessage(String message)12969 public Builder addMessage(String message) { 12970 mMessages.add(message); 12971 return this; 12972 } 12973 12974 /** 12975 * Sets the pending intent and remote input which will convey the reply to this 12976 * notification. 12977 * 12978 * @param pendingIntent The pending intent which will be triggered on a reply. 12979 * @param remoteInput The remote input parcelable which will carry the reply. 12980 * @return This object for method chaining. 12981 * 12982 * @see CarExtender.UnreadConversation#getRemoteInput 12983 * @see CarExtender.UnreadConversation#getReplyPendingIntent 12984 */ setReplyAction( PendingIntent pendingIntent, RemoteInput remoteInput)12985 public Builder setReplyAction( 12986 PendingIntent pendingIntent, RemoteInput remoteInput) { 12987 mRemoteInput = remoteInput; 12988 mReplyPendingIntent = pendingIntent; 12989 12990 return this; 12991 } 12992 12993 /** 12994 * Sets the pending intent that will be sent once the messages in this notification 12995 * are read. 12996 * 12997 * @param pendingIntent The pending intent to use. 12998 * @return This object for method chaining. 12999 */ setReadPendingIntent(PendingIntent pendingIntent)13000 public Builder setReadPendingIntent(PendingIntent pendingIntent) { 13001 mReadPendingIntent = pendingIntent; 13002 return this; 13003 } 13004 13005 /** 13006 * Sets the timestamp of the most recent message in an unread conversation. 13007 * 13008 * If a messaging notification has been posted by your application and has not 13009 * yet been cancelled, posting a later notification with the same id and tag 13010 * but without a newer timestamp may result in Android Auto not displaying a 13011 * heads up notification for the later notification. 13012 * 13013 * @param timestamp The timestamp of the most recent message in the conversation. 13014 * @return This object for method chaining. 13015 */ setLatestTimestamp(long timestamp)13016 public Builder setLatestTimestamp(long timestamp) { 13017 mLatestTimestamp = timestamp; 13018 return this; 13019 } 13020 13021 /** 13022 * Builds a new unread conversation object. 13023 * 13024 * @return The new unread conversation object. 13025 */ build()13026 public UnreadConversation build() { 13027 String[] messages = mMessages.toArray(new String[mMessages.size()]); 13028 String[] participants = { mParticipant }; 13029 return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent, 13030 mReadPendingIntent, participants, mLatestTimestamp); 13031 } 13032 } 13033 } 13034 13035 /** 13036 * <p>Helper class to add Android TV extensions to notifications. To create a notification 13037 * with a TV extension: 13038 * 13039 * <ol> 13040 * <li>Create an {@link Notification.Builder}, setting any desired properties. 13041 * <li>Create a {@link TvExtender}. 13042 * <li>Set TV-specific properties using the {@code set} methods of 13043 * {@link TvExtender}. 13044 * <li>Call {@link Notification.Builder#extend(Notification.Extender)} 13045 * to apply the extension to a notification. 13046 * </ol> 13047 * 13048 * <pre class="prettyprint"> 13049 * Notification notification = new Notification.Builder(context) 13050 * ... 13051 * .extend(new TvExtender() 13052 * .set*(...)) 13053 * .build(); 13054 * </pre> 13055 * 13056 * <p>TV extensions can be accessed on an existing notification by using the 13057 * {@code TvExtender(Notification)} constructor, and then using the {@code get} methods 13058 * to access values. 13059 */ 13060 @FlaggedApi(Flags.FLAG_API_TVEXTENDER) 13061 public static final class TvExtender implements Extender { 13062 private static final String TAG = "TvExtender"; 13063 13064 private static final String EXTRA_TV_EXTENDER = "android.tv.EXTENSIONS"; 13065 private static final String EXTRA_FLAGS = "flags"; 13066 static final String EXTRA_CONTENT_INTENT = "content_intent"; 13067 static final String EXTRA_DELETE_INTENT = "delete_intent"; 13068 private static final String EXTRA_CHANNEL_ID = "channel_id"; 13069 private static final String EXTRA_SUPPRESS_SHOW_OVER_APPS = "suppressShowOverApps"; 13070 13071 // Flags bitwise-ored to mFlags 13072 private static final int FLAG_AVAILABLE_ON_TV = 0x1; 13073 13074 private int mFlags; 13075 private String mChannelId; 13076 private PendingIntent mContentIntent; 13077 private PendingIntent mDeleteIntent; 13078 private boolean mSuppressShowOverApps; 13079 13080 /** 13081 * Create a {@link TvExtender} with default options. 13082 */ TvExtender()13083 public TvExtender() { 13084 mFlags = FLAG_AVAILABLE_ON_TV; 13085 } 13086 13087 /** 13088 * Create a {@link TvExtender} from the TvExtender options of an existing Notification. 13089 * 13090 * @param notif The notification from which to copy options. 13091 */ TvExtender(@onNull Notification notif)13092 public TvExtender(@NonNull Notification notif) { 13093 Bundle bundle = notif.extras == null ? 13094 null : notif.extras.getBundle(EXTRA_TV_EXTENDER); 13095 if (bundle != null) { 13096 mFlags = bundle.getInt(EXTRA_FLAGS); 13097 mChannelId = bundle.getString(EXTRA_CHANNEL_ID); 13098 mSuppressShowOverApps = bundle.getBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS); 13099 mContentIntent = bundle.getParcelable(EXTRA_CONTENT_INTENT, PendingIntent.class); 13100 mDeleteIntent = bundle.getParcelable(EXTRA_DELETE_INTENT, PendingIntent.class); 13101 } 13102 } 13103 13104 /** 13105 * Apply a TV extension to a notification that is being built. This is typically called by 13106 * the {@link Notification.Builder#extend(Notification.Extender)} 13107 * method of {@link Notification.Builder}. 13108 */ 13109 @Override 13110 @NonNull extend(@onNull Notification.Builder builder)13111 public Notification.Builder extend(@NonNull Notification.Builder builder) { 13112 Bundle bundle = new Bundle(); 13113 13114 bundle.putInt(EXTRA_FLAGS, mFlags); 13115 bundle.putString(EXTRA_CHANNEL_ID, mChannelId); 13116 bundle.putBoolean(EXTRA_SUPPRESS_SHOW_OVER_APPS, mSuppressShowOverApps); 13117 if (mContentIntent != null) { 13118 bundle.putParcelable(EXTRA_CONTENT_INTENT, mContentIntent); 13119 } 13120 13121 if (mDeleteIntent != null) { 13122 bundle.putParcelable(EXTRA_DELETE_INTENT, mDeleteIntent); 13123 } 13124 13125 builder.getExtras().putBundle(EXTRA_TV_EXTENDER, bundle); 13126 return builder; 13127 } 13128 13129 /** 13130 * Returns true if this notification should be shown on TV. This method returns true 13131 * if the notification was extended with a TvExtender. 13132 */ isAvailableOnTv()13133 public boolean isAvailableOnTv() { 13134 return (mFlags & FLAG_AVAILABLE_ON_TV) != 0; 13135 } 13136 13137 /** 13138 * Specifies the channel the notification should be delivered on when shown on TV. 13139 * It can be different from the channel that the notification is delivered to when 13140 * posting on a non-TV device. Prefer to use {@link setChannelId(String)}. 13141 * 13142 * @hide 13143 */ 13144 @SystemApi setChannel(String channelId)13145 public TvExtender setChannel(String channelId) { 13146 mChannelId = channelId; 13147 return this; 13148 } 13149 13150 /** 13151 * Specifies the channel the notification should be delivered on when shown on TV. 13152 * It can be different from the channel that the notification is delivered to when 13153 * posting on a non-TV device. 13154 * 13155 * @return this object for method chaining 13156 */ 13157 @NonNull setChannelId(@ullable String channelId)13158 public TvExtender setChannelId(@Nullable String channelId) { 13159 mChannelId = channelId; 13160 return this; 13161 } 13162 13163 /** 13164 * @removed 13165 * @hide 13166 */ 13167 @Deprecated 13168 @SystemApi getChannel()13169 public String getChannel() { 13170 return mChannelId; 13171 } 13172 13173 /** 13174 * Returns the id of the channel this notification posts to on TV. 13175 */ 13176 @Nullable getChannelId()13177 public String getChannelId() { 13178 return mChannelId; 13179 } 13180 13181 /** 13182 * Supplies a {@link PendingIntent} to be sent when the notification is selected on TV. 13183 * If provided, it is used instead of the content intent specified 13184 * at the level of Notification. 13185 * 13186 * @param intent the {@link PendingIntent} for the associated notification content 13187 * @return this object for method chaining 13188 */ 13189 @NonNull setContentIntent(@ullable PendingIntent intent)13190 public TvExtender setContentIntent(@Nullable PendingIntent intent) { 13191 mContentIntent = intent; 13192 return this; 13193 } 13194 13195 /** 13196 * Returns the TV-specific content intent. If this method returns null, the 13197 * main content intent on the notification should be used. 13198 * 13199 * @see Notification#contentIntent 13200 */ 13201 @Nullable getContentIntent()13202 public PendingIntent getContentIntent() { 13203 return mContentIntent; 13204 } 13205 13206 /** 13207 * Supplies a {@link PendingIntent} to send when the notification is cleared explicitly 13208 * by the user on TV. If provided, it is used instead of the delete intent specified 13209 * at the level of Notification. 13210 * 13211 * @param intent the {@link PendingIntent} for the associated notification deletion 13212 * @return this object for method chaining 13213 */ 13214 @NonNull setDeleteIntent(@ullable PendingIntent intent)13215 public TvExtender setDeleteIntent(@Nullable PendingIntent intent) { 13216 mDeleteIntent = intent; 13217 return this; 13218 } 13219 13220 /** 13221 * Returns the TV-specific delete intent. If this method returns null, the 13222 * main delete intent on the notification should be used. 13223 * 13224 * @see Notification#deleteIntent 13225 */ 13226 @Nullable getDeleteIntent()13227 public PendingIntent getDeleteIntent() { 13228 return mDeleteIntent; 13229 } 13230 13231 /** 13232 * Specifies whether this notification should suppress showing a message over top of apps 13233 * outside of the launcher. 13234 * 13235 * @param suppress whether the notification should suppress showing over apps. 13236 * @return this object for method chaining 13237 */ 13238 @NonNull setSuppressShowOverApps(boolean suppress)13239 public TvExtender setSuppressShowOverApps(boolean suppress) { 13240 mSuppressShowOverApps = suppress; 13241 return this; 13242 } 13243 13244 /** 13245 * Returns true if this notification should not show messages over top of apps 13246 * outside of the launcher. 13247 * 13248 * @hide 13249 */ 13250 @SystemApi getSuppressShowOverApps()13251 public boolean getSuppressShowOverApps() { 13252 return mSuppressShowOverApps; 13253 } 13254 13255 /** 13256 * Returns true if this notification should not show messages over top of apps 13257 * outside of the launcher. 13258 */ isSuppressShowOverApps()13259 public boolean isSuppressShowOverApps() { 13260 return mSuppressShowOverApps; 13261 } 13262 } 13263 13264 /** 13265 * Get an array of Parcelable objects from a parcelable array bundle field. 13266 * Update the bundle to have a typed array so fetches in the future don't need 13267 * to do an array copy. 13268 */ 13269 @Nullable getParcelableArrayFromBundle( Bundle bundle, String key, Class<T> itemClass)13270 private static <T extends Parcelable> T[] getParcelableArrayFromBundle( 13271 Bundle bundle, String key, Class<T> itemClass) { 13272 final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class); 13273 final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass(); 13274 if (arrayClass.isInstance(array) || array == null) { 13275 return (T[]) array; 13276 } 13277 final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length); 13278 for (int i = 0; i < array.length; i++) { 13279 typedArray[i] = (T) array[i]; 13280 } 13281 bundle.putParcelableArray(key, typedArray); 13282 return typedArray; 13283 } 13284 13285 private static class BuilderRemoteViews extends RemoteViews { BuilderRemoteViews(Parcel parcel)13286 public BuilderRemoteViews(Parcel parcel) { 13287 super(parcel); 13288 } 13289 BuilderRemoteViews(ApplicationInfo appInfo, int layoutId)13290 public BuilderRemoteViews(ApplicationInfo appInfo, int layoutId) { 13291 super(appInfo, layoutId); 13292 } 13293 13294 @Override clone()13295 public BuilderRemoteViews clone() { 13296 Parcel p = Parcel.obtain(); 13297 writeToParcel(p, 0); 13298 p.setDataPosition(0); 13299 BuilderRemoteViews brv = new BuilderRemoteViews(p); 13300 p.recycle(); 13301 return brv; 13302 } 13303 13304 /** 13305 * Override and return true, since {@link RemoteViews#onLoadClass(Class)} is not overridden. 13306 * 13307 * @see RemoteViews#shouldUseStaticFilter() 13308 */ 13309 @Override shouldUseStaticFilter()13310 protected boolean shouldUseStaticFilter() { 13311 return true; 13312 } 13313 } 13314 13315 /** 13316 * A result object where information about the template that was created is saved. 13317 */ 13318 private static class TemplateBindResult { 13319 boolean mRightIconVisible; 13320 float mRightIconWidthDp; 13321 float mRightIconHeightDp; 13322 13323 /** 13324 * The margin end that needs to be added to the heading so that it won't overlap 13325 * with the large icon. This value includes the space required to accommodate the large 13326 * icon, but should be added to the space needed to accommodate the expander. This does 13327 * not include the 16dp content margin that all notification views must have. 13328 */ 13329 public final MarginSet mHeadingExtraMarginSet = new MarginSet(); 13330 13331 /** 13332 * The margin end that needs to be added to the heading so that it won't overlap 13333 * with the large icon. This value includes the space required to accommodate the large 13334 * icon as well as the expander. This does not include the 16dp content margin that all 13335 * notification views must have. 13336 */ 13337 public final MarginSet mHeadingFullMarginSet = new MarginSet(); 13338 13339 /** 13340 * The margin end that needs to be added to the title text of the big state 13341 * so that it won't overlap with the large icon, but assuming the text can run under 13342 * the expander when that icon is not visible. 13343 */ 13344 public final MarginSet mTitleMarginSet = new MarginSet(); 13345 setRightIconState(boolean visible, float widthDp, float heightDp, float marginEndDpIfVisible, float expanderSizeDp)13346 public void setRightIconState(boolean visible, float widthDp, float heightDp, 13347 float marginEndDpIfVisible, float expanderSizeDp) { 13348 mRightIconVisible = visible; 13349 mRightIconWidthDp = widthDp; 13350 mRightIconHeightDp = heightDp; 13351 mHeadingExtraMarginSet.setValues(0, marginEndDpIfVisible); 13352 mHeadingFullMarginSet.setValues(expanderSizeDp, marginEndDpIfVisible + expanderSizeDp); 13353 mTitleMarginSet.setValues(0, marginEndDpIfVisible + expanderSizeDp); 13354 } 13355 13356 /** 13357 * This contains the end margins for a view when the right icon is visible or not. These 13358 * values are both needed so that NotificationGroupingUtil can 'move' the right_icon to the 13359 * left_icon and adjust the margins, and to undo that change as well. 13360 */ 13361 private class MarginSet { 13362 private float mValueIfGone; 13363 private float mValueIfVisible; 13364 setValues(float valueIfGone, float valueIfVisible)13365 public void setValues(float valueIfGone, float valueIfVisible) { 13366 mValueIfGone = valueIfGone; 13367 mValueIfVisible = valueIfVisible; 13368 } 13369 applyToView(@onNull RemoteViews views, @IdRes int viewId)13370 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId) { 13371 applyToView(views, viewId, 0); 13372 } 13373 applyToView(@onNull RemoteViews views, @IdRes int viewId, float extraMarginDp)13374 public void applyToView(@NonNull RemoteViews views, @IdRes int viewId, 13375 float extraMarginDp) { 13376 final float marginEndDp = getDpValue() + extraMarginDp; 13377 if (viewId == R.id.notification_header) { 13378 views.setFloat(R.id.notification_header, 13379 "setTopLineExtraMarginEndDp", marginEndDp); 13380 } else if (viewId == R.id.text || viewId == R.id.big_text) { 13381 if (mValueIfGone != 0) { 13382 throw new RuntimeException("Programming error: `text` and `big_text` use " 13383 + "ImageFloatingTextView which can either show a margin or not; " 13384 + "thus mValueIfGone must be 0, but it was " + mValueIfGone); 13385 } 13386 // Note that the caller must set "setNumIndentLines" to a positive int in order 13387 // for this margin to do anything at all. 13388 views.setFloat(viewId, "setImageEndMarginDp", mValueIfVisible); 13389 views.setBoolean(viewId, "setHasImage", mRightIconVisible); 13390 // Apply just the *extra* margin as the view layout margin; this will be 13391 // unchanged depending on the visibility of the image, but it means that the 13392 // extra margin applies to *every* line of text instead of just indented lines. 13393 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 13394 extraMarginDp, TypedValue.COMPLEX_UNIT_DIP); 13395 } else { 13396 views.setViewLayoutMargin(viewId, RemoteViews.MARGIN_END, 13397 marginEndDp, TypedValue.COMPLEX_UNIT_DIP); 13398 } 13399 if (mRightIconVisible) { 13400 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_visible, 13401 TypedValue.createComplexDimension( 13402 mValueIfVisible + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 13403 views.setIntTag(viewId, R.id.tag_margin_end_when_icon_gone, 13404 TypedValue.createComplexDimension( 13405 mValueIfGone + extraMarginDp, TypedValue.COMPLEX_UNIT_DIP)); 13406 } 13407 } 13408 getDpValue()13409 public float getDpValue() { 13410 return mRightIconVisible ? mValueIfVisible : mValueIfGone; 13411 } 13412 } 13413 } 13414 13415 private static class StandardTemplateParams { 13416 /** 13417 * Notifications will be minimally decorated with ONLY an icon and expander: 13418 * <li>A large icon is never shown. 13419 * <li>A progress bar is never shown. 13420 * <li>The expanded and heads up states do not show actions, even if provided. 13421 */ 13422 public static final int DECORATION_MINIMAL = 1; 13423 13424 /** 13425 * Notifications will be partially decorated with AT LEAST an icon and expander: 13426 * <li>A large icon is shown if provided. 13427 * <li>A progress bar is shown if provided and enough space remains below the content. 13428 * <li>Actions are shown in the expanded and heads up states. 13429 */ 13430 public static final int DECORATION_PARTIAL = 2; 13431 13432 public static int VIEW_TYPE_UNSPECIFIED = 0; 13433 public static int VIEW_TYPE_NORMAL = 1; 13434 public static int VIEW_TYPE_BIG = 2; 13435 public static int VIEW_TYPE_HEADS_UP = 3; 13436 public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state 13437 public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version 13438 public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group 13439 13440 int mViewType = VIEW_TYPE_UNSPECIFIED; 13441 boolean mHeaderless; 13442 boolean mHideAppName; 13443 boolean mHideTitle; 13444 boolean mHideSubText; 13445 boolean mHideTime; 13446 boolean mHideActions; 13447 boolean mHideProgress; 13448 boolean mHideSnoozeButton; 13449 boolean mHideLeftIcon; 13450 boolean mHideRightIcon; 13451 Icon mPromotedPicture; 13452 boolean mCallStyleActions; 13453 boolean mAllowTextWithProgress; 13454 int mTitleViewId; 13455 int mTextViewId; 13456 @Nullable CharSequence mTitle; 13457 @Nullable CharSequence mText; 13458 @Nullable CharSequence mHeaderTextSecondary; 13459 @Nullable CharSequence mSubText; 13460 int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 13461 boolean allowColorization = true; 13462 boolean mHighlightExpander = false; 13463 reset()13464 final StandardTemplateParams reset() { 13465 mViewType = VIEW_TYPE_UNSPECIFIED; 13466 mHeaderless = false; 13467 mHideAppName = false; 13468 mHideTitle = false; 13469 mHideSubText = false; 13470 mHideTime = false; 13471 mHideActions = false; 13472 mHideProgress = false; 13473 mHideSnoozeButton = false; 13474 mHideLeftIcon = false; 13475 mHideRightIcon = false; 13476 mPromotedPicture = null; 13477 mCallStyleActions = false; 13478 mAllowTextWithProgress = false; 13479 mTitleViewId = R.id.title; 13480 mTextViewId = R.id.text; 13481 mTitle = null; 13482 mText = null; 13483 mSubText = null; 13484 mHeaderTextSecondary = null; 13485 maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES; 13486 allowColorization = true; 13487 mHighlightExpander = false; 13488 return this; 13489 } 13490 hasTitle()13491 final boolean hasTitle() { 13492 return !TextUtils.isEmpty(mTitle) && !mHideTitle; 13493 } 13494 viewType(int viewType)13495 final StandardTemplateParams viewType(int viewType) { 13496 mViewType = viewType; 13497 return this; 13498 } 13499 headerless(boolean headerless)13500 public StandardTemplateParams headerless(boolean headerless) { 13501 mHeaderless = headerless; 13502 return this; 13503 } 13504 hideAppName(boolean hideAppName)13505 public StandardTemplateParams hideAppName(boolean hideAppName) { 13506 mHideAppName = hideAppName; 13507 return this; 13508 } 13509 hideSubText(boolean hideSubText)13510 public StandardTemplateParams hideSubText(boolean hideSubText) { 13511 mHideSubText = hideSubText; 13512 return this; 13513 } 13514 hideTime(boolean hideTime)13515 public StandardTemplateParams hideTime(boolean hideTime) { 13516 mHideTime = hideTime; 13517 return this; 13518 } 13519 hideActions(boolean hideActions)13520 final StandardTemplateParams hideActions(boolean hideActions) { 13521 this.mHideActions = hideActions; 13522 return this; 13523 } 13524 hideProgress(boolean hideProgress)13525 final StandardTemplateParams hideProgress(boolean hideProgress) { 13526 this.mHideProgress = hideProgress; 13527 return this; 13528 } 13529 hideTitle(boolean hideTitle)13530 final StandardTemplateParams hideTitle(boolean hideTitle) { 13531 this.mHideTitle = hideTitle; 13532 return this; 13533 } 13534 callStyleActions(boolean callStyleActions)13535 final StandardTemplateParams callStyleActions(boolean callStyleActions) { 13536 this.mCallStyleActions = callStyleActions; 13537 return this; 13538 } 13539 allowTextWithProgress(boolean allowTextWithProgress)13540 final StandardTemplateParams allowTextWithProgress(boolean allowTextWithProgress) { 13541 this.mAllowTextWithProgress = allowTextWithProgress; 13542 return this; 13543 } 13544 hideSnoozeButton(boolean hideSnoozeButton)13545 final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) { 13546 this.mHideSnoozeButton = hideSnoozeButton; 13547 return this; 13548 } 13549 promotedPicture(Icon promotedPicture)13550 final StandardTemplateParams promotedPicture(Icon promotedPicture) { 13551 this.mPromotedPicture = promotedPicture; 13552 return this; 13553 } 13554 titleViewId(int titleViewId)13555 public StandardTemplateParams titleViewId(int titleViewId) { 13556 mTitleViewId = titleViewId; 13557 return this; 13558 } 13559 textViewId(int textViewId)13560 public StandardTemplateParams textViewId(int textViewId) { 13561 mTextViewId = textViewId; 13562 return this; 13563 } 13564 title(@ullable CharSequence title)13565 final StandardTemplateParams title(@Nullable CharSequence title) { 13566 this.mTitle = title; 13567 return this; 13568 } 13569 text(@ullable CharSequence text)13570 final StandardTemplateParams text(@Nullable CharSequence text) { 13571 this.mText = text; 13572 return this; 13573 } 13574 summaryText(@ullable CharSequence text)13575 final StandardTemplateParams summaryText(@Nullable CharSequence text) { 13576 this.mSubText = text; 13577 return this; 13578 } 13579 headerTextSecondary(@ullable CharSequence text)13580 final StandardTemplateParams headerTextSecondary(@Nullable CharSequence text) { 13581 this.mHeaderTextSecondary = text; 13582 return this; 13583 } 13584 13585 hideLeftIcon(boolean hideLeftIcon)13586 final StandardTemplateParams hideLeftIcon(boolean hideLeftIcon) { 13587 this.mHideLeftIcon = hideLeftIcon; 13588 return this; 13589 } 13590 hideRightIcon(boolean hideRightIcon)13591 final StandardTemplateParams hideRightIcon(boolean hideRightIcon) { 13592 this.mHideRightIcon = hideRightIcon; 13593 return this; 13594 } 13595 disallowColorization()13596 final StandardTemplateParams disallowColorization() { 13597 this.allowColorization = false; 13598 return this; 13599 } 13600 highlightExpander(boolean highlight)13601 final StandardTemplateParams highlightExpander(boolean highlight) { 13602 this.mHighlightExpander = highlight; 13603 return this; 13604 } 13605 fillTextsFrom(Builder b)13606 final StandardTemplateParams fillTextsFrom(Builder b) { 13607 Bundle extras = b.mN.extras; 13608 this.mTitle = b.processLegacyText(extras.getCharSequence(EXTRA_TITLE)); 13609 this.mText = b.processLegacyText(extras.getCharSequence(EXTRA_TEXT)); 13610 this.mSubText = extras.getCharSequence(EXTRA_SUB_TEXT); 13611 return this; 13612 } 13613 13614 /** 13615 * Set the maximum lines of remote input history lines allowed. 13616 * @param maxRemoteInputHistory The number of lines. 13617 * @return The builder for method chaining. 13618 */ setMaxRemoteInputHistory(int maxRemoteInputHistory)13619 public StandardTemplateParams setMaxRemoteInputHistory(int maxRemoteInputHistory) { 13620 this.maxRemoteInputHistory = maxRemoteInputHistory; 13621 return this; 13622 } 13623 decorationType(int decorationType)13624 public StandardTemplateParams decorationType(int decorationType) { 13625 hideTitle(true); 13626 // Minimally decorated custom views do not show certain pieces of chrome that have 13627 // always been shown when using DecoratedCustomViewStyle. 13628 boolean hideOtherFields = decorationType <= DECORATION_MINIMAL; 13629 hideLeftIcon(false); // The left icon decoration is better than showing nothing. 13630 hideRightIcon(hideOtherFields); 13631 hideProgress(hideOtherFields); 13632 hideActions(hideOtherFields); 13633 return this; 13634 } 13635 } 13636 13637 /** 13638 * A utility which stores and calculates the palette of colors used to color notifications. 13639 * @hide 13640 */ 13641 @VisibleForTesting 13642 public static class Colors { 13643 private int mPaletteIsForRawColor = COLOR_INVALID; 13644 private boolean mPaletteIsForColorized = false; 13645 private boolean mPaletteIsForNightMode = false; 13646 // The following colors are the palette 13647 private int mBackgroundColor = COLOR_INVALID; 13648 private int mProtectionColor = COLOR_INVALID; 13649 private int mPrimaryTextColor = COLOR_INVALID; 13650 private int mSecondaryTextColor = COLOR_INVALID; 13651 private int mPrimaryAccentColor = COLOR_INVALID; 13652 private int mSecondaryAccentColor = COLOR_INVALID; 13653 private int mTertiaryAccentColor = COLOR_INVALID; 13654 private int mOnTertiaryAccentTextColor = COLOR_INVALID; 13655 private int mTertiaryFixedDimAccentColor = COLOR_INVALID; 13656 private int mOnTertiaryFixedAccentTextColor = COLOR_INVALID; 13657 13658 private int mErrorColor = COLOR_INVALID; 13659 private int mContrastColor = COLOR_INVALID; 13660 private int mRippleAlpha = 0x33; 13661 13662 /** 13663 * A utility for obtaining a TypedArray of the given DayNight-styled attributes, which 13664 * returns null when the context is a mock with no theme. 13665 * 13666 * NOTE: Calling this method is expensive, as creating a new ContextThemeWrapper 13667 * instances can allocate as much as 5MB of memory, so its important to call this method 13668 * only when necessary, getting as many attributes as possible from each call. 13669 * 13670 * @see Resources.Theme#obtainStyledAttributes(int[]) 13671 */ 13672 @Nullable obtainDayNightAttributes(@onNull Context ctx, @NonNull @StyleableRes int[] attrs)13673 private static TypedArray obtainDayNightAttributes(@NonNull Context ctx, 13674 @NonNull @StyleableRes int[] attrs) { 13675 // when testing, the mock context may have no theme 13676 if (ctx.getTheme() == null) { 13677 return null; 13678 } 13679 Resources.Theme theme = new ContextThemeWrapper(ctx, 13680 R.style.Theme_DeviceDefault_DayNight).getTheme(); 13681 return theme.obtainStyledAttributes(attrs); 13682 } 13683 13684 /** A null-safe wrapper of TypedArray.getColor because mocks return null */ getColor(@ullable TypedArray ta, int index, @ColorInt int defValue)13685 private static @ColorInt int getColor(@Nullable TypedArray ta, int index, 13686 @ColorInt int defValue) { 13687 return ta == null ? defValue : ta.getColor(index, defValue); 13688 } 13689 13690 /** 13691 * Resolve the palette. If the inputs have not changed, this will be a no-op. 13692 * This does not handle invalidating the resolved colors when the context itself changes, 13693 * because that case does not happen in the current notification inflation pipeline; we will 13694 * recreate a new builder (and thus a new palette) when reinflating notifications for a new 13695 * theme (admittedly, we do the same for night mode, but that's easy to check). 13696 * 13697 * @param ctx the builder context. 13698 * @param rawColor the notification's color; may be COLOR_DEFAULT, but may never have alpha. 13699 * @param isColorized whether the notification is colorized. 13700 * @param nightMode whether the UI is in night mode. 13701 */ resolvePalette(Context ctx, int rawColor, boolean isColorized, boolean nightMode)13702 public void resolvePalette(Context ctx, int rawColor, 13703 boolean isColorized, boolean nightMode) { 13704 if (mPaletteIsForRawColor == rawColor 13705 && mPaletteIsForColorized == isColorized 13706 && mPaletteIsForNightMode == nightMode) { 13707 return; 13708 } 13709 mPaletteIsForRawColor = rawColor; 13710 mPaletteIsForColorized = isColorized; 13711 mPaletteIsForNightMode = nightMode; 13712 13713 if (isColorized) { 13714 if (rawColor == COLOR_DEFAULT) { 13715 int[] attrs = {R.attr.materialColorSecondary}; 13716 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 13717 mBackgroundColor = getColor(ta, 0, Color.WHITE); 13718 } 13719 } else { 13720 mBackgroundColor = rawColor; 13721 } 13722 mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 13723 ContrastColorUtil.resolvePrimaryColor(ctx, mBackgroundColor, nightMode), 13724 mBackgroundColor, 4.5); 13725 mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast( 13726 ContrastColorUtil.resolveSecondaryColor(ctx, mBackgroundColor, nightMode), 13727 mBackgroundColor, 4.5); 13728 mContrastColor = mPrimaryTextColor; 13729 mPrimaryAccentColor = mPrimaryTextColor; 13730 mSecondaryAccentColor = mSecondaryTextColor; 13731 mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor); 13732 mOnTertiaryAccentTextColor = mBackgroundColor; 13733 mTertiaryFixedDimAccentColor = mTertiaryAccentColor; 13734 mOnTertiaryFixedAccentTextColor = mOnTertiaryAccentTextColor; 13735 mErrorColor = mPrimaryTextColor; 13736 mRippleAlpha = 0x33; 13737 } else { 13738 int[] attrs = { 13739 R.attr.materialColorSurfaceContainerHigh, 13740 R.attr.materialColorOnSurface, 13741 R.attr.materialColorOnSurfaceVariant, 13742 R.attr.materialColorPrimary, 13743 R.attr.materialColorSecondary, 13744 R.attr.materialColorTertiary, 13745 R.attr.materialColorOnTertiary, 13746 R.attr.materialColorTertiaryFixedDim, 13747 R.attr.materialColorOnTertiaryFixed, 13748 R.attr.colorError, 13749 R.attr.colorControlHighlight 13750 }; 13751 try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) { 13752 mBackgroundColor = getColor(ta, 0, nightMode ? Color.BLACK : Color.WHITE); 13753 mPrimaryTextColor = getColor(ta, 1, COLOR_INVALID); 13754 mSecondaryTextColor = getColor(ta, 2, COLOR_INVALID); 13755 mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID); 13756 mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID); 13757 mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID); 13758 mOnTertiaryAccentTextColor = getColor(ta, 6, COLOR_INVALID); 13759 mTertiaryFixedDimAccentColor = getColor(ta, 7, COLOR_INVALID); 13760 mOnTertiaryFixedAccentTextColor = getColor(ta, 8, COLOR_INVALID); 13761 mErrorColor = getColor(ta, 9, COLOR_INVALID); 13762 mRippleAlpha = Color.alpha(getColor(ta, 10, 0x33ffffff)); 13763 } 13764 mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor, 13765 mBackgroundColor, nightMode); 13766 13767 // make sure every color has a valid value 13768 if (mPrimaryTextColor == COLOR_INVALID) { 13769 mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor( 13770 ctx, mBackgroundColor, nightMode); 13771 } 13772 if (mSecondaryTextColor == COLOR_INVALID) { 13773 mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor( 13774 ctx, mBackgroundColor, nightMode); 13775 } 13776 if (mPrimaryAccentColor == COLOR_INVALID) { 13777 mPrimaryAccentColor = mContrastColor; 13778 } 13779 if (mSecondaryAccentColor == COLOR_INVALID) { 13780 mSecondaryAccentColor = mContrastColor; 13781 } 13782 if (mTertiaryAccentColor == COLOR_INVALID) { 13783 mTertiaryAccentColor = mContrastColor; 13784 } 13785 if (mOnTertiaryAccentTextColor == COLOR_INVALID) { 13786 mOnTertiaryAccentTextColor = ColorUtils.setAlphaComponent( 13787 ContrastColorUtil.resolvePrimaryColor( 13788 ctx, mTertiaryAccentColor, nightMode), 0xFF); 13789 } 13790 if (mTertiaryFixedDimAccentColor == COLOR_INVALID) { 13791 mTertiaryFixedDimAccentColor = mContrastColor; 13792 } 13793 if (mOnTertiaryFixedAccentTextColor == COLOR_INVALID) { 13794 mOnTertiaryFixedAccentTextColor = ColorUtils.setAlphaComponent( 13795 ContrastColorUtil.resolvePrimaryColor( 13796 ctx, mTertiaryFixedDimAccentColor, nightMode), 0xFF); 13797 } 13798 if (mErrorColor == COLOR_INVALID) { 13799 mErrorColor = mPrimaryTextColor; 13800 } 13801 } 13802 // make sure every color has a valid value 13803 mProtectionColor = ColorUtils.blendARGB(mPrimaryTextColor, mBackgroundColor, 0.9f); 13804 } 13805 13806 /** calculates the contrast color for the non-colorized notifications */ calculateContrastColor(Context ctx, @ColorInt int rawColor, @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode)13807 private static @ColorInt int calculateContrastColor(Context ctx, @ColorInt int rawColor, 13808 @ColorInt int accentColor, @ColorInt int backgroundColor, boolean nightMode) { 13809 int color; 13810 if (rawColor == COLOR_DEFAULT) { 13811 color = accentColor; 13812 if (color == COLOR_INVALID) { 13813 color = ContrastColorUtil.resolveDefaultColor(ctx, backgroundColor, nightMode); 13814 } 13815 } else { 13816 color = ContrastColorUtil.resolveContrastColor(ctx, rawColor, backgroundColor, 13817 nightMode); 13818 } 13819 return flattenAlpha(color, backgroundColor); 13820 } 13821 13822 /** remove any alpha by manually blending it with the given background. */ flattenAlpha(@olorInt int color, @ColorInt int background)13823 private static @ColorInt int flattenAlpha(@ColorInt int color, @ColorInt int background) { 13824 return Color.alpha(color) == 0xff ? color 13825 : ContrastColorUtil.compositeColors(color, background); 13826 } 13827 13828 /** @return the notification's background color */ getBackgroundColor()13829 public @ColorInt int getBackgroundColor() { 13830 return mBackgroundColor; 13831 } 13832 13833 /** 13834 * @return the "surface protection" color from the theme, 13835 * or a variant of the normal background color when colorized. 13836 */ getProtectionColor()13837 public @ColorInt int getProtectionColor() { 13838 return mProtectionColor; 13839 } 13840 13841 /** @return the color for the most prominent text */ getPrimaryTextColor()13842 public @ColorInt int getPrimaryTextColor() { 13843 return mPrimaryTextColor; 13844 } 13845 13846 /** @return the color for less prominent text */ getSecondaryTextColor()13847 public @ColorInt int getSecondaryTextColor() { 13848 return mSecondaryTextColor; 13849 } 13850 13851 /** @return the theme's accent color for colored UI elements. */ getPrimaryAccentColor()13852 public @ColorInt int getPrimaryAccentColor() { 13853 return mPrimaryAccentColor; 13854 } 13855 13856 /** @return the theme's secondary accent color for colored UI elements. */ getSecondaryAccentColor()13857 public @ColorInt int getSecondaryAccentColor() { 13858 return mSecondaryAccentColor; 13859 } 13860 13861 /** @return the theme's tertiary accent color for colored UI elements. */ getTertiaryAccentColor()13862 public @ColorInt int getTertiaryAccentColor() { 13863 return mTertiaryAccentColor; 13864 } 13865 13866 /** @return the theme's text color to be used on the tertiary accent color. */ getOnTertiaryAccentTextColor()13867 public @ColorInt int getOnTertiaryAccentTextColor() { 13868 return mOnTertiaryAccentTextColor; 13869 } 13870 13871 /** @return the theme's tertiary fixed dim accent color for colored UI elements. */ getTertiaryFixedDimAccentColor()13872 public @ColorInt int getTertiaryFixedDimAccentColor() { 13873 return mTertiaryFixedDimAccentColor; 13874 } 13875 13876 /** @return the theme's text color to be used on the tertiary fixed accent color. */ getOnTertiaryFixedAccentTextColor()13877 public @ColorInt int getOnTertiaryFixedAccentTextColor() { 13878 return mOnTertiaryFixedAccentTextColor; 13879 } 13880 13881 /** 13882 * @return the contrast-adjusted version of the color provided by the app, or the 13883 * primary text color when colorized. 13884 */ getContrastColor()13885 public @ColorInt int getContrastColor() { 13886 return mContrastColor; 13887 } 13888 13889 /** @return the theme's error color, or the primary text color when colorized. */ getErrorColor()13890 public @ColorInt int getErrorColor() { 13891 return mErrorColor; 13892 } 13893 13894 /** @return the alpha component of the current theme's control highlight color. */ getRippleAlpha()13895 public int getRippleAlpha() { 13896 return mRippleAlpha; 13897 } 13898 } 13899 } 13900